diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index ecbb3e182..3ad9aef36 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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. +pub 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; } @@ -1010,7 +1076,7 @@ const PostMessageCallback = struct { } }; -const FunctionSetter = union(enum) { +pub const FunctionSetter = union(enum) { func: js.Function.Global, anything: js.Value, }; @@ -1018,7 +1084,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 @@ -1076,6 +1142,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, .{}); diff --git a/src/browser/webapi/element/html/Body.zig b/src/browser/webapi/element/html/Body.zig index faed2adf8..464ea83be 100644 --- a/src/browser/webapi/element/html/Body.zig +++ b/src/browser/webapi/element/html/Body.zig @@ -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,15 +38,50 @@ 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. +pub fn getOnBlur(_: *Body, frame: *Frame) ?js.Function.Global { + return frame.window._on_blur; +} +pub fn setOnBlur(_: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_blur = Window.getFunctionFromSetter(setter); +} + +pub fn getOnError(_: *Body, frame: *Frame) ?js.Function.Global { + return frame.window._on_error; +} +pub fn setOnError(_: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_error = Window.getFunctionFromSetter(setter); +} + +pub fn getOnFocus(_: *Body, frame: *Frame) ?js.Function.Global { + return frame.window._on_focus; +} +pub fn setOnFocus(_: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_focus = Window.getFunctionFromSetter(setter); } -/// 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 setOnLoad(_: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_load = Window.getFunctionFromSetter(setter); +} + +pub fn getOnResize(_: *Body, frame: *Frame) ?js.Function.Global { + return frame.window._on_resize; +} +pub fn setOnResize(_: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_resize = Window.getFunctionFromSetter(setter); +} + +pub fn getOnScroll(_: *Body, frame: *Frame) ?js.Function.Global { + return frame.window._on_scroll; +} +pub fn setOnScroll(_: *Body, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_scroll = Window.getFunctionFromSetter(setter); +} pub const JsApi = struct { pub const bridge = js.Bridge(Body); @@ -56,17 +92,33 @@ 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 }); + inline for (window_reflecting_attributes) |attr| { + if (el.getAttributeSafe(comptime .wrap(attr))) |value| { + frame.window.setWindowReflectingHandlerFromAttribute(comptime .wrap(attr), value, frame); + } } } + + pub fn attributeChange(_: *Element, name: String, value: String, frame: *Frame) !void { + frame.window.setWindowReflectingHandlerFromAttribute(name, value.str(), frame); + } + + pub fn attributeRemove(_: *Element, name: String, frame: *Frame) !void { + frame.window.setWindowReflectingHandlerFromAttribute(name, null, frame); + } }; diff --git a/src/browser/webapi/element/html/FrameSet.zig b/src/browser/webapi/element/html/FrameSet.zig index 4e84a25c3..bec26401f 100644 --- a/src/browser/webapi/element/html/FrameSet.zig +++ b/src/browser/webapi/element/html/FrameSet.zig @@ -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,51 @@ 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. +pub fn getOnBlur(_: *FrameSet, frame: *Frame) ?js.Function.Global { + return frame.window._on_blur; +} +pub fn setOnBlur(_: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_blur = Window.getFunctionFromSetter(setter); +} + +pub fn getOnError(_: *FrameSet, frame: *Frame) ?js.Function.Global { + return frame.window._on_error; +} +pub fn setOnError(_: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_error = Window.getFunctionFromSetter(setter); +} + +pub fn getOnFocus(_: *FrameSet, frame: *Frame) ?js.Function.Global { + return frame.window._on_focus; +} +pub fn setOnFocus(_: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_focus = Window.getFunctionFromSetter(setter); +} + +pub fn getOnLoad(_: *FrameSet, frame: *Frame) ?js.Function.Global { + return frame.window._on_load; +} +pub fn setOnLoad(_: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_load = Window.getFunctionFromSetter(setter); +} + +pub fn getOnResize(_: *FrameSet, frame: *Frame) ?js.Function.Global { + return frame.window._on_resize; +} +pub fn setOnResize(_: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_resize = Window.getFunctionFromSetter(setter); +} + +pub fn getOnScroll(_: *FrameSet, frame: *Frame) ?js.Function.Global { + return frame.window._on_scroll; +} +pub fn setOnScroll(_: *FrameSet, setter: ?Window.FunctionSetter, frame: *Frame) !void { + frame.window._on_scroll = Window.getFunctionFromSetter(setter); +} + pub fn getCols(self: *FrameSet) []const u8 { return self.asElement().getAttributeSafe(comptime .wrap("cols")) orelse ""; } @@ -42,6 +92,36 @@ 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); + inline for (window_reflecting_attributes) |attr| { + if (el.getAttributeSafe(comptime .wrap(attr))) |value| { + frame.window.setWindowReflectingHandlerFromAttribute(comptime .wrap(attr), value, frame); + } + } + } + + pub fn attributeChange(_: *Element, name: String, value: String, frame: *Frame) !void { + frame.window.setWindowReflectingHandlerFromAttribute(name, value.str(), frame); + } + + pub fn attributeRemove(_: *Element, name: String, frame: *Frame) !void { + frame.window.setWindowReflectingHandlerFromAttribute(name, null, frame); + } }; const testing = @import("../../../../testing.zig");