From 169582c992df672ddcc1bd869fa48097e8e903c9 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 30 Dec 2025 09:33:00 +0800 Subject: [PATCH] DOMRect constructor More default computed styles (color and backgroundColor) HTMLMetaElement properties Case-insensitive findAdjacentNodes position parameter Allow computedStyle pseudo_element parameter (ignore, log not implemented) Window.isSecureContext always returns false --- src/browser/webapi/DOMRect.zig | 24 ++++++++---- src/browser/webapi/Document.zig | 2 +- src/browser/webapi/Element.zig | 12 ------ src/browser/webapi/IntersectionObserver.zig | 8 ---- src/browser/webapi/Node.zig | 10 ++--- src/browser/webapi/Window.zig | 14 ++++++- .../webapi/css/CSSStyleDeclaration.zig | 20 ++++++++++ src/browser/webapi/element/Html.zig | 11 ++++-- src/browser/webapi/element/html/Meta.zig | 38 +++++++++++++++++++ 9 files changed, 100 insertions(+), 39 deletions(-) diff --git a/src/browser/webapi/DOMRect.zig b/src/browser/webapi/DOMRect.zig index 49a72e1d..b331da28 100644 --- a/src/browser/webapi/DOMRect.zig +++ b/src/browser/webapi/DOMRect.zig @@ -18,16 +18,23 @@ const DOMRect = @This(); +const std = @import("std"); const js = @import("../js/js.zig"); +const Page = @import("../Page.zig"); _x: f64, _y: f64, _width: f64, _height: f64, -_top: f64, -_right: f64, -_bottom: f64, -_left: f64, + +pub fn init(x: f64, y: f64, width: f64, height: f64, page: *Page) !*DOMRect { + return page._factory.create(DOMRect{ + ._x = x, + ._y = y, + ._width = width, + ._height = height, + }); +} pub fn getX(self: *DOMRect) f64 { return self._x; @@ -46,19 +53,19 @@ pub fn getHeight(self: *DOMRect) f64 { } pub fn getTop(self: *DOMRect) f64 { - return self._top; + return @min(self._y, self._y + self._height); } pub fn getRight(self: *DOMRect) f64 { - return self._right; + return @max(self._x, self._x + self._width); } pub fn getBottom(self: *DOMRect) f64 { - return self._bottom; + return @max(self._y, self._y + self._height); } pub fn getLeft(self: *DOMRect) f64 { - return self._left; + return @min(self._x, self._x + self._width); } pub const JsApi = struct { @@ -70,6 +77,7 @@ pub const JsApi = struct { pub var class_id: bridge.ClassId = undefined; }; + pub const constructor = bridge.constructor(DOMRect.init, .{}); pub const x = bridge.accessor(DOMRect.getX, null, .{}); pub const y = bridge.accessor(DOMRect.getY, null, .{}); pub const width = bridge.accessor(DOMRect.getWidth, null, .{}); diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 32024bb2..f79ccc73 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -392,7 +392,7 @@ pub fn elementFromPoint(self: *Document, x: f64, y: f64, page: *Page) !?*Element if (node.is(Element)) |element| { if (try element.checkVisibility(page)) { const rect = try element.getBoundingClientRect(page); - if (x >= rect._left and x <= rect._right and y >= rect._top and y <= rect._bottom) { + if (x >= rect.getLeft() and x <= rect.getRight() and y >= rect.getTop() and y <= rect.getBottom()) { topmost = element; } } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 04492056..4de815e5 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -821,10 +821,6 @@ pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect { ._y = 0.0, ._width = 0.0, ._height = 0.0, - ._top = 0.0, - ._right = 0.0, - ._bottom = 0.0, - ._left = 0.0, }); } @@ -833,20 +829,12 @@ pub fn getBoundingClientRect(self: *Element, page: *Page) !*DOMRect { // Use sibling position for x coordinate to ensure siblings have different x values const x = calculateSiblingPosition(self.asNode()); - const top = y; - const left = x; - const right = x + dims.width; - const bottom = y + dims.height; return page._factory.create(DOMRect{ ._x = x, ._y = y, ._width = dims.width, ._height = dims.height, - ._top = top, - ._right = right, - ._bottom = bottom, - ._left = left, }); } diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index 7cf65d0c..5bc94428 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -44,10 +44,6 @@ var zero_rect: DOMRect = .{ ._y = 0.0, ._width = 0.0, ._height = 0.0, - ._top = 0.0, - ._right = 0.0, - ._bottom = 0.0, - ._left = 0.0, }; pub const ObserverInit = struct { @@ -152,10 +148,6 @@ fn calculateIntersection( ._y = 0.0, ._width = 1920.0, ._height = 1080.0, - ._top = 0.0, - ._right = 1920.0, - ._bottom = 1080.0, - ._left = 0.0, }); // For a headless browser without real layout, we treat all elements as fully visible. diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index b0a9c2b3..79277f07 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -120,19 +120,19 @@ pub fn is(self: *Node, comptime T: type) ?*T { /// * `target_node` is `*Node` (where we actually insert), /// * `previous_node` is `?*Node`. pub fn findAdjacentNodes(self: *Node, position: []const u8) !struct { *Node, ?*Node } { - // Prefer case-sensitive match. + // Case-insensitive match per HTML spec. // "beforeend" was the most common case in my tests; we might adjust the order // depending on which ones websites prefer most. - if (std.mem.eql(u8, position, "beforeend")) { + if (std.ascii.eqlIgnoreCase(position, "beforeend")) { return .{ self, null }; } - if (std.mem.eql(u8, position, "afterbegin")) { + if (std.ascii.eqlIgnoreCase(position, "afterbegin")) { // Get the first child; null indicates there are no children. return .{ self, self.firstChild() }; } - if (std.mem.eql(u8, position, "beforebegin")) { + if (std.ascii.eqlIgnoreCase(position, "beforebegin")) { // The node must have a parent node in order to use this variant. const parent_node = self.parentNode() orelse return error.NoModificationAllowed; // Parent cannot be Document. @@ -144,7 +144,7 @@ pub fn findAdjacentNodes(self: *Node, position: []const u8) !struct { *Node, ?*N return .{ parent_node, self }; } - if (std.mem.eql(u8, position, "afterend")) { + if (std.ascii.eqlIgnoreCase(position, "afterend")) { // The node must have a parent node in order to use this variant. const parent_node = self.parentNode() orelse return error.NoModificationAllowed; // Parent cannot be Document. diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 2aaff9f7..c5dcce98 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -299,10 +299,21 @@ pub fn matchMedia(_: *const Window, query: []const u8, page: *Page) !*MediaQuery }); } -pub fn getComputedStyle(_: *const Window, element: *Element, page: *Page) !*CSSStyleProperties { +pub fn getComputedStyle(_: *const Window, element: *Element, pseudo_element: ?[]const u8, page: *Page) !*CSSStyleProperties { + if (pseudo_element) |pe| { + log.warn(.not_implemented, "window.GetComputedStyle", .{ .pseudo_element = pe }); + } return CSSStyleProperties.init(element, true, page); } +pub fn getIsSecureContext(_: *const Window) bool { + // Return false since we don't have secure-context-only APIs implemented + // (webcam, geolocation, clipboard, etc.) + // This is safer and could help avoid processing errors by hinting at + // sites not to try to access those features + return false; +} + pub fn postMessage(self: *Window, message: js.Object, target_origin: ?[]const u8, page: *Page) !void { // For now, we ignore targetOrigin checking and just dispatch the message // In a full implementation, we would validate the origin @@ -666,6 +677,7 @@ pub const JsApi = struct { pub const atob = bridge.function(Window.atob, .{}); pub const reportError = bridge.function(Window.reportError, .{}); pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{}); + pub const isSecureContext = bridge.accessor(Window.getIsSecureContext, null, .{}); pub const frames = bridge.accessor(Window.getWindow, null, .{ .cache = "frames" }); pub const index = bridge.indexed(Window.getFrame, .{ .null_as_undefined = true }); pub const length = bridge.accessor(Window.getFramesLength, null, .{ .cache = "length" }); diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 7a4c271c..24c49025 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -219,6 +219,14 @@ fn getDefaultPropertyValue(self: *const CSSStyleDeclaration, normalized_name: [] const element = self._element orelse return ""; return getDefaultDisplay(element); } + if (std.mem.eql(u8, normalized_name, "color")) { + const element = self._element orelse return ""; + return getDefaultColor(element); + } + if (std.mem.eql(u8, normalized_name, "background-color")) { + // transparent + return "rgba(0, 0, 0, 0)"; + } return ""; } @@ -256,6 +264,18 @@ fn isInlineTag(tag_name: []const u8) bool { return false; } +fn getDefaultColor(element: *const Element) []const u8 { + switch (element._type) { + .html => |html| { + return switch (html._type) { + .anchor => "rgb(0, 0, 238)", // blue + else => "rgb(0, 0, 0)", + }; + }, + .svg => return "rgb(0, 0, 0)", + } +} + pub const Property = struct { _name: String, _value: String, diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index fc033441..7bab0f3e 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -258,10 +258,10 @@ pub fn setInnerText(self: *HtmlElement, text: []const u8, page: *Page) !void { pub fn insertAdjacentHTML( self: *HtmlElement, position: []const u8, - /// TODO: Add support for XML parsing. - html_or_xml: []const u8, + html: []const u8, page: *Page, ) !void { + // Create a new HTMLDocument. const doc = try page._factory.document(@import("../HTMLDocument.zig"){ ._proto = undefined, @@ -270,9 +270,12 @@ pub fn insertAdjacentHTML( const Parser = @import("../../parser/Parser.zig"); var parser = Parser.init(page.call_arena, doc_node, page); - parser.parse(html_or_xml); + parser.parse(html); + // Check if there's parsing error. - if (parser.err) |_| return error.Invalid; + if (parser.err) |_| { + return error.Invalid; + } // We always get it wrapped like so: // { ... } diff --git a/src/browser/webapi/element/html/Meta.zig b/src/browser/webapi/element/html/Meta.zig index 900d4932..9efddfbd 100644 --- a/src/browser/webapi/element/html/Meta.zig +++ b/src/browser/webapi/element/html/Meta.zig @@ -17,6 +17,7 @@ // along with this program. If not, see . const js = @import("../../../js/js.zig"); +const Page = @import("../../../Page.zig"); const Node = @import("../../Node.zig"); const Element = @import("../../Element.zig"); const HtmlElement = @import("../Html.zig"); @@ -35,6 +36,38 @@ pub fn asNode(self: *Meta) *Node { return self.asElement().asNode(); } +pub fn getName(self: *Meta) []const u8 { + return self.asElement().getAttributeSafe("name") orelse return ""; +} + +pub fn setName(self: *Meta, value: []const u8, page: *Page) !void { + try self.asElement().setAttributeSafe("name", value, page); +} + +pub fn getHttpEquiv(self: *Meta) []const u8 { + return self.asElement().getAttributeSafe("http-equiv") orelse return ""; +} + +pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void { + try self.asElement().setAttributeSafe("http-equiv", value, page); +} + +pub fn getContent(self: *Meta) []const u8 { + return self.asElement().getAttributeSafe("content") orelse return ""; +} + +pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void { + try self.asElement().setAttributeSafe("content", value, page); +} + +pub fn getMedia(self: *Meta) []const u8 { + return self.asElement().getAttributeSafe("media") orelse return ""; +} + +pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void { + try self.asElement().setAttributeSafe("media", value, page); +} + pub const JsApi = struct { pub const bridge = js.Bridge(MetaElement); @@ -43,4 +76,9 @@ pub const JsApi = struct { pub const prototype_chain = bridge.prototypeChain(); pub var class_id: bridge.ClassId = undefined; }; + + pub const name = bridge.accessor(MetaElement.getName, MetaElement.setName, .{}); + pub const httpEquiv = bridge.accessor(MetaElement.getHttpEquiv, MetaElement.setHttpEquiv, .{}); + pub const content = bridge.accessor(MetaElement.getContent, MetaElement.setContent, .{}); + pub const media = bridge.accessor(MetaElement.getMedia, MetaElement.setMedia, .{}); };