mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-01 18:26:06 -04:00
Merge pull request #2944 from lightpanda-io/dom-pr-03-handler-plumbing
webapi: listener option validation, window-reflecting handlers, exception propagation
This commit is contained in:
@@ -515,6 +515,10 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any
|
||||
|
||||
const js_err: *const v8.Value = switch (err) {
|
||||
error.TryCatchRethrow => return,
|
||||
// A JS exception is already pending in the isolate (e.g. a value's
|
||||
// toString threw during argument conversion); throwing anything here
|
||||
// would replace the original exception the script expects to see.
|
||||
error.JsException => return,
|
||||
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
||||
error.TypeError => isolate.createTypeError(""),
|
||||
error.RangeError => isolate.createRangeError(""),
|
||||
@@ -1016,6 +1020,11 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info:
|
||||
// type instantiation of jsValueToZig may not include such errors
|
||||
// in its inferred error set.
|
||||
@field(args, tupleFieldName(field_index)) = local.jsValueToZig(param.type.?, js_val) catch |err| {
|
||||
if (err == error.JsException) {
|
||||
// an exception thrown by user code (e.g. a toString
|
||||
// getter) is pending; propagate it untouched
|
||||
return err;
|
||||
}
|
||||
const DOMException = @import("../webapi/DOMException.zig");
|
||||
if (DOMException.fromError(err) != null) {
|
||||
return err;
|
||||
|
||||
6
src/browser/tests/frames/support/window_reflecting.html
Normal file
6
src/browser/tests/frames/support/window_reflecting.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- The onresize attribute exercises the parse-time path: it must land on
|
||||
THIS frame's window, not the top frame's. -->
|
||||
<body onresize="window.__attr_resize = true">
|
||||
<p>x</p>
|
||||
</body>
|
||||
53
src/browser/tests/frames/window_reflecting_handlers.html
Normal file
53
src/browser/tests/frames/window_reflecting_handlers.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<!--
|
||||
The window-reflecting body element event handlers (onblur, onerror, onfocus,
|
||||
onload, onresize, onscroll) alias the Window of the body's node document. A
|
||||
same-origin script reaching into another frame (parent setting a handler on
|
||||
an iframe's body) must target the iframe's window, not the window of the
|
||||
realm running the assignment.
|
||||
-->
|
||||
<iframe id="wrh" src="support/window_reflecting.html"></iframe>
|
||||
|
||||
<script id="cross_realm_property_handlers">
|
||||
testing.onload(() => {
|
||||
const idf = document.getElementById('wrh');
|
||||
const ibody = idf.contentDocument.body;
|
||||
const iwin = idf.contentWindow;
|
||||
|
||||
const cb = () => {};
|
||||
ibody.onblur = cb;
|
||||
testing.expectEqual(cb, ibody.onblur);
|
||||
testing.expectEqual(cb, iwin.onblur);
|
||||
// the parent's window (the caller's realm) must be untouched
|
||||
testing.expectTrue(window.onblur == null);
|
||||
testing.expectTrue(document.body.onblur == null);
|
||||
|
||||
ibody.onblur = null;
|
||||
testing.expectTrue(iwin.onblur == null);
|
||||
});
|
||||
</script>
|
||||
|
||||
<script id="cross_realm_attribute_handlers">
|
||||
testing.onload(() => {
|
||||
const idf = document.getElementById('wrh');
|
||||
const ibody = idf.contentDocument.body;
|
||||
const iwin = idf.contentWindow;
|
||||
|
||||
// Parse-time: the onresize attribute in the iframe's markup was compiled
|
||||
// onto the iframe's window while the parent frame was the active one.
|
||||
testing.expectEqual('function', typeof iwin.onresize);
|
||||
testing.expectTrue(window.onresize == null);
|
||||
|
||||
// Runtime: setting the content attribute from the parent realm.
|
||||
ibody.setAttribute('onscroll', 'window.__parent_set = true');
|
||||
testing.expectEqual('function', typeof iwin.onscroll);
|
||||
testing.expectTrue(window.onscroll == null);
|
||||
|
||||
ibody.removeAttribute('onscroll');
|
||||
testing.expectTrue(iwin.onscroll == null);
|
||||
});
|
||||
</script>
|
||||
@@ -23,6 +23,7 @@ const Page = @import("../Page.zig");
|
||||
const EventManager = @import("../EventManager.zig");
|
||||
|
||||
const Event = @import("Event.zig");
|
||||
const AbortSignal = @import("AbortSignal.zig");
|
||||
|
||||
const RegisterOptions = EventManager.RegisterOptions;
|
||||
|
||||
@@ -83,7 +84,17 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, exec: *js.Execution) !bo
|
||||
|
||||
const AddEventListenerOptions = union(enum) {
|
||||
capture: bool,
|
||||
options: RegisterOptions,
|
||||
options: Options,
|
||||
|
||||
// The signal is kept as a raw js.Value so that an explicit null (or any
|
||||
// non-AbortSignal value) can be told apart from an absent or undefined
|
||||
// member, and rejected with a TypeError per the dictionary conversion.
|
||||
const Options = struct {
|
||||
once: bool = false,
|
||||
capture: bool = false,
|
||||
passive: bool = false,
|
||||
signal: ?js.Value = null,
|
||||
};
|
||||
};
|
||||
|
||||
pub const EventListenerCallback = union(enum) {
|
||||
@@ -91,6 +102,27 @@ pub const EventListenerCallback = union(enum) {
|
||||
object: js.Object,
|
||||
};
|
||||
pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventListenerCallback, opts_: ?AddEventListenerOptions, exec: *js.Execution) !void {
|
||||
// Convert the options before the null-callback early return: per spec,
|
||||
// the dictionary conversion throws even when the callback is null.
|
||||
const options = blk: {
|
||||
const o = opts_ orelse break :blk RegisterOptions{};
|
||||
break :blk switch (o) {
|
||||
.options => |opts| RegisterOptions{
|
||||
.once = opts.once,
|
||||
.capture = opts.capture,
|
||||
.passive = opts.passive,
|
||||
.signal = signal: {
|
||||
const signal = opts.signal orelse break :signal null;
|
||||
if (signal.isUndefined()) {
|
||||
break :signal null;
|
||||
}
|
||||
break :signal try signal.toZig(*AbortSignal);
|
||||
},
|
||||
},
|
||||
.capture => |capture| RegisterOptions{ .capture = capture },
|
||||
};
|
||||
};
|
||||
|
||||
const callback = callback_ orelse return;
|
||||
|
||||
const em_callback: EventManager.Callback = switch (callback) {
|
||||
@@ -98,14 +130,6 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi
|
||||
.function => |func| .{ .function = func },
|
||||
};
|
||||
|
||||
const options = blk: {
|
||||
const o = opts_ orelse break :blk RegisterOptions{};
|
||||
break :blk switch (o) {
|
||||
.options => |opts| opts,
|
||||
.capture => |capture| RegisterOptions{ .capture = capture },
|
||||
};
|
||||
};
|
||||
|
||||
switch (exec.js.global) {
|
||||
inline else => |g| _ = try g._event_manager.register(self, typ, em_callback, options),
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ _on_pageshow: ?js.Function.Global = null,
|
||||
_on_popstate: ?js.Function.Global = null,
|
||||
_on_hashchange: ?js.Function.Global = null,
|
||||
_on_error: ?js.Function.Global = null,
|
||||
_on_blur: ?js.Function.Global = null,
|
||||
_on_focus: ?js.Function.Global = null,
|
||||
_on_resize: ?js.Function.Global = null,
|
||||
_on_scroll: ?js.Function.Global = null,
|
||||
_on_message: ?js.Function.Global = null,
|
||||
_on_rejection_handled: ?js.Function.Global = null,
|
||||
_on_unhandled_rejection: ?js.Function.Global = null,
|
||||
@@ -388,6 +392,68 @@ pub fn setOnError(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_error = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnBlur(self: *const Window) ?js.Function.Global {
|
||||
return self._on_blur;
|
||||
}
|
||||
|
||||
pub fn setOnBlur(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_blur = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnFocus(self: *const Window) ?js.Function.Global {
|
||||
return self._on_focus;
|
||||
}
|
||||
|
||||
pub fn setOnFocus(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_focus = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnResize(self: *const Window) ?js.Function.Global {
|
||||
return self._on_resize;
|
||||
}
|
||||
|
||||
pub fn setOnResize(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_resize = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnScroll(self: *const Window) ?js.Function.Global {
|
||||
return self._on_scroll;
|
||||
}
|
||||
|
||||
pub fn setOnScroll(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_scroll = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
// The "window-reflecting body element event handler set" (HTML spec): these
|
||||
// event handlers of body and frameset elements are aliases for the Window's.
|
||||
// Returns the Window storage slot for the given content attribute name, or
|
||||
// null if the attribute isn't part of the set.
|
||||
fn windowReflectingHandler(self: *Window, name: lp.String) ?*?js.Function.Global {
|
||||
if (name.eql(comptime .wrap("onblur"))) return &self._on_blur;
|
||||
if (name.eql(comptime .wrap("onerror"))) return &self._on_error;
|
||||
if (name.eql(comptime .wrap("onfocus"))) return &self._on_focus;
|
||||
if (name.eql(comptime .wrap("onload"))) return &self._on_load;
|
||||
if (name.eql(comptime .wrap("onresize"))) return &self._on_resize;
|
||||
if (name.eql(comptime .wrap("onscroll"))) return &self._on_scroll;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Applies a window-reflecting content attribute (set on a body or frameset
|
||||
// element) to the Window's event handler. A null value clears the handler.
|
||||
pub fn setWindowReflectingHandlerFromAttribute(self: *Window, name: lp.String, value: ?[]const u8, frame: *Frame) void {
|
||||
const slot = self.windowReflectingHandler(name) orelse return;
|
||||
const expr = value orelse {
|
||||
slot.* = null;
|
||||
return;
|
||||
};
|
||||
if (frame.js.stringToPersistedFunction(expr, &.{"event"}, &.{})) |func| {
|
||||
slot.* = func;
|
||||
} else |err| {
|
||||
log.err(.js, "window reflecting handler", .{ .err = err, .str = expr });
|
||||
slot.* = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getOnMessage(self: *const Window) ?js.Function.Global {
|
||||
return self._on_message;
|
||||
}
|
||||
@@ -1013,7 +1079,7 @@ const PostMessageCallback = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const FunctionSetter = union(enum) {
|
||||
pub const FunctionSetter = union(enum) {
|
||||
func: js.Function.Global,
|
||||
anything: js.Value,
|
||||
};
|
||||
@@ -1021,7 +1087,7 @@ const FunctionSetter = union(enum) {
|
||||
// window.onload = {}; doesn't fail, but it doesn't do anything.
|
||||
// seems like setting to null is ok (though, at least on Firefix, it preserves
|
||||
// the original value, which we could do, but why?)
|
||||
fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
|
||||
pub fn getFunctionFromSetter(setter_: ?FunctionSetter) ?js.Function.Global {
|
||||
const setter = setter_ orelse return null;
|
||||
return switch (setter) {
|
||||
.func => |func| func, // Already a Global from bridge auto-conversion
|
||||
@@ -1079,6 +1145,10 @@ pub const JsApi = struct {
|
||||
pub const onpopstate = bridge.accessor(Window.getOnPopState, Window.setOnPopState, .{});
|
||||
pub const onhashchange = bridge.accessor(Window.getOnHashChange, Window.setOnHashChange, .{});
|
||||
pub const onerror = bridge.accessor(Window.getOnError, Window.setOnError, .{});
|
||||
pub const onblur = bridge.accessor(Window.getOnBlur, Window.setOnBlur, .{});
|
||||
pub const onfocus = bridge.accessor(Window.getOnFocus, Window.setOnFocus, .{});
|
||||
pub const onresize = bridge.accessor(Window.getOnResize, Window.setOnResize, .{});
|
||||
pub const onscroll = bridge.accessor(Window.getOnScroll, Window.setOnScroll, .{});
|
||||
pub const onmessage = bridge.accessor(Window.getOnMessage, Window.setOnMessage, .{});
|
||||
pub const onrejectionhandled = bridge.accessor(Window.getOnRejectionHandled, Window.setOnRejectionHandled, .{});
|
||||
pub const onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{});
|
||||
|
||||
@@ -212,13 +212,9 @@ pub const JsApi = struct {
|
||||
configurable: bool,
|
||||
};
|
||||
|
||||
fn getIndexes(self: *HTMLCollection, exec: *const Execution) !js.Array {
|
||||
const frame = switch (exec.js.global) {
|
||||
.frame => |f| f,
|
||||
.worker => unreachable,
|
||||
};
|
||||
fn getIndexes(self: *HTMLCollection, frame: *Frame) !js.Array {
|
||||
const len = self.length(frame);
|
||||
var arr = exec.js.local.?.newArray(len);
|
||||
var arr = frame.js.local.?.newArray(len);
|
||||
for (0..len) |i| {
|
||||
_ = try arr.set(@intCast(i), i, .{});
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ const Frame = @import("../../../Frame.zig");
|
||||
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Window = @import("../../Window.zig");
|
||||
const HtmlElement = @import("../Html.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const String = lp.String;
|
||||
|
||||
const Body = @This();
|
||||
|
||||
@@ -37,14 +38,57 @@ pub fn asNode(self: *Body) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
/// Special-case: `body.onload` is actually an alias for `window.onload`.
|
||||
pub fn setOnLoad(_: *Body, callback: ?js.Function.Global, frame: *Frame) !void {
|
||||
frame.window._on_load = callback;
|
||||
// Special-case: the "window-reflecting body element event handler set"
|
||||
// (blur, error, focus, load, resize, scroll) are aliases for the Window's
|
||||
// event handlers.
|
||||
|
||||
// The aliased Window is the one of the element's node document's frame — not
|
||||
// the caller's frame, which differs when a same-origin script reaches into
|
||||
// another frame (e.g. the parent setting iframeDoc.body.onblur).
|
||||
fn reflectedWindow(self: *Body, frame: *Frame) *Window {
|
||||
return self.asElement().ownerFrame(frame).window;
|
||||
}
|
||||
|
||||
/// Special-case: `body.onload` is actually an alias for `window.onload`.
|
||||
pub fn getOnLoad(_: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return frame.window._on_load;
|
||||
pub fn getOnBlur(self: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_blur;
|
||||
}
|
||||
pub fn setOnBlur(self: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_blur = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnError(self: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_error;
|
||||
}
|
||||
pub fn setOnError(self: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_error = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnFocus(self: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_focus;
|
||||
}
|
||||
pub fn setOnFocus(self: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_focus = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnLoad(self: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_load;
|
||||
}
|
||||
pub fn setOnLoad(self: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_load = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnResize(self: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_resize;
|
||||
}
|
||||
pub fn setOnResize(self: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_resize = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnScroll(self: *Body, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_scroll;
|
||||
}
|
||||
pub fn setOnScroll(self: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_scroll = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
@@ -56,17 +100,36 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const onblur = bridge.accessor(getOnBlur, setOnBlur, .{ .null_as_undefined = false });
|
||||
pub const onerror = bridge.accessor(getOnError, setOnError, .{ .null_as_undefined = false });
|
||||
pub const onfocus = bridge.accessor(getOnFocus, setOnFocus, .{ .null_as_undefined = false });
|
||||
pub const onload = bridge.accessor(getOnLoad, setOnLoad, .{ .null_as_undefined = false });
|
||||
pub const onresize = bridge.accessor(getOnResize, setOnResize, .{ .null_as_undefined = false });
|
||||
pub const onscroll = bridge.accessor(getOnScroll, setOnScroll, .{ .null_as_undefined = false });
|
||||
};
|
||||
|
||||
pub const Build = struct {
|
||||
const window_reflecting_attributes = [_][]const u8{
|
||||
"onblur", "onerror", "onfocus", "onload", "onresize", "onscroll",
|
||||
};
|
||||
|
||||
pub fn complete(node: *Node, frame: *Frame) !void {
|
||||
const el = node.as(Element);
|
||||
const on_load = el.getAttributeSafe(comptime .wrap("onload")) orelse return;
|
||||
if (frame.js.stringToPersistedFunction(on_load, &.{"event"}, &.{})) |func| {
|
||||
frame.window._on_load = func;
|
||||
} else |err| {
|
||||
log.err(.js, "body.onload", .{ .err = err, .str = on_load });
|
||||
const owner = node.ownerFrame(frame);
|
||||
inline for (window_reflecting_attributes) |attr| {
|
||||
if (el.getAttributeSafe(comptime .wrap(attr))) |value| {
|
||||
owner.window.setWindowReflectingHandlerFromAttribute(comptime .wrap(attr), value, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributeChange(el: *Element, name: String, value: String, frame: *Frame) !void {
|
||||
const owner = el.ownerFrame(frame);
|
||||
owner.window.setWindowReflectingHandlerFromAttribute(name, value.str(), owner);
|
||||
}
|
||||
|
||||
pub fn attributeRemove(el: *Element, name: String, frame: *Frame) !void {
|
||||
const owner = el.ownerFrame(frame);
|
||||
owner.window.setWindowReflectingHandlerFromAttribute(name, null, owner);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Window = @import("../../Window.zig");
|
||||
const HtmlElement = @import("../Html.zig");
|
||||
|
||||
const String = lp.String;
|
||||
|
||||
const FrameSet = @This();
|
||||
|
||||
_proto: *HtmlElement,
|
||||
@@ -15,6 +20,59 @@ pub fn asNode(self: *FrameSet) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
// Special-case: the "window-reflecting body element event handler set"
|
||||
// (blur, error, focus, load, resize, scroll) are aliases for the Window's
|
||||
// event handlers, on frameset elements just like on body elements.
|
||||
|
||||
// The aliased Window is the one of the element's node document's frame — not
|
||||
// the caller's frame, which differs when a same-origin script reaches into
|
||||
// another frame (e.g. the parent setting a handler on a child frameset).
|
||||
fn reflectedWindow(self: *FrameSet, frame: *Frame) *Window {
|
||||
return self.asElement().ownerFrame(frame).window;
|
||||
}
|
||||
|
||||
pub fn getOnBlur(self: *FrameSet, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_blur;
|
||||
}
|
||||
pub fn setOnBlur(self: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_blur = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnError(self: *FrameSet, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_error;
|
||||
}
|
||||
pub fn setOnError(self: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_error = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnFocus(self: *FrameSet, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_focus;
|
||||
}
|
||||
pub fn setOnFocus(self: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_focus = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnLoad(self: *FrameSet, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_load;
|
||||
}
|
||||
pub fn setOnLoad(self: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_load = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnResize(self: *FrameSet, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_resize;
|
||||
}
|
||||
pub fn setOnResize(self: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_resize = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getOnScroll(self: *FrameSet, frame: *Frame) ?js.Function.Global {
|
||||
return self.reflectedWindow(frame)._on_scroll;
|
||||
}
|
||||
pub fn setOnScroll(self: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
self.reflectedWindow(frame)._on_scroll = Window.getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn getCols(self: *FrameSet) []const u8 {
|
||||
return self.asElement().getAttributeSafe(comptime .wrap("cols")) orelse "";
|
||||
}
|
||||
@@ -42,6 +100,39 @@ pub const JsApi = struct {
|
||||
|
||||
pub const cols = bridge.accessor(FrameSet.getCols, FrameSet.setCols, .{ .ce_reactions = true });
|
||||
pub const rows = bridge.accessor(FrameSet.getRows, FrameSet.setRows, .{ .ce_reactions = true });
|
||||
|
||||
pub const onblur = bridge.accessor(getOnBlur, setOnBlur, .{ .null_as_undefined = false });
|
||||
pub const onerror = bridge.accessor(getOnError, setOnError, .{ .null_as_undefined = false });
|
||||
pub const onfocus = bridge.accessor(getOnFocus, setOnFocus, .{ .null_as_undefined = false });
|
||||
pub const onload = bridge.accessor(getOnLoad, setOnLoad, .{ .null_as_undefined = false });
|
||||
pub const onresize = bridge.accessor(getOnResize, setOnResize, .{ .null_as_undefined = false });
|
||||
pub const onscroll = bridge.accessor(getOnScroll, setOnScroll, .{ .null_as_undefined = false });
|
||||
};
|
||||
|
||||
pub const Build = struct {
|
||||
const window_reflecting_attributes = [_][]const u8{
|
||||
"onblur", "onerror", "onfocus", "onload", "onresize", "onscroll",
|
||||
};
|
||||
|
||||
pub fn complete(node: *Node, frame: *Frame) !void {
|
||||
const el = node.as(Element);
|
||||
const owner = node.ownerFrame(frame);
|
||||
inline for (window_reflecting_attributes) |attr| {
|
||||
if (el.getAttributeSafe(comptime .wrap(attr))) |value| {
|
||||
owner.window.setWindowReflectingHandlerFromAttribute(comptime .wrap(attr), value, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributeChange(el: *Element, name: String, value: String, frame: *Frame) !void {
|
||||
const owner = el.ownerFrame(frame);
|
||||
owner.window.setWindowReflectingHandlerFromAttribute(name, value.str(), owner);
|
||||
}
|
||||
|
||||
pub fn attributeRemove(el: *Element, name: String, frame: *Frame) !void {
|
||||
const owner = el.ownerFrame(frame);
|
||||
owner.window.setWindowReflectingHandlerFromAttribute(name, null, owner);
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../../../testing.zig");
|
||||
|
||||
Reference in New Issue
Block a user