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
This commit is contained in:
Karl Seguin
2025-12-30 09:33:00 +08:00
parent 7b74161e9c
commit 169582c992
9 changed files with 100 additions and 39 deletions

View File

@@ -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, .{});

View File

@@ -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;
}
}

View File

@@ -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,
});
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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" });

View File

@@ -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,

View File

@@ -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:
// <html><head></head><body>{ ... }</body></html>

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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, .{});
};