From a451fe42485be25e10d3a71af07ce4d5a398a544 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 7 Feb 2026 15:28:53 +0800 Subject: [PATCH] Add a number of [simple] missing properties --- src/browser/js/bridge.zig | 23 ++++++++++++++ src/browser/webapi/Navigator.zig | 10 +++++++ src/browser/webapi/element/html/Image.zig | 14 +++++++++ src/browser/webapi/element/html/Link.zig | 12 ++++++++ src/browser/webapi/element/html/Script.zig | 35 ++++++++++++++++++++++ 5 files changed, 94 insertions(+) diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 4571cff1..36710adc 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -437,11 +437,21 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8 } if (comptime IS_DEBUG) { + if (std.mem.startsWith(u8, property, "__")) { + // some frameworks will extend built-in types using a __ prefix + // these should always be safe to ignore. + return 0; + } + const ignored = std.StaticStringMap(void).initComptime(.{ + .{ "Deno", {} }, .{ "process", {} }, .{ "ShadyDOM", {} }, .{ "ShadyCSS", {} }, + // a lot of sites seem to like having their own window.config. + .{ "config", {} }, + .{ "litNonce", {} }, .{ "litHtmlVersions", {} }, .{ "litElementVersions", {} }, @@ -457,6 +467,8 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8 .{ "__google_recaptcha_client", {} }, .{ "CLOSURE_FLAGS", {} }, + .{ "__REACT_DEVTOOLS_GLOBAL_HOOK__", {} }, + .{ "ApplePaySession", {} }, }); if (!ignored.has(property)) { log.debug(.unknown_prop, "unknown global property", .{ @@ -512,6 +524,17 @@ pub fn unknownObjectPropertyCallback(comptime JsApi: type) *const fn (?*const v8 } } + if (JsApi == @import("../webapi/element/Html.zig") or JsApi == @import("../webapi/Element.zig") or JsApi == @import("../webapi/element/html/Custom.zig")) { + // react ? + if (std.mem.eql(u8, property, "props")) return 0; + if (std.mem.eql(u8, property, "hydrated")) return 0; + if (std.mem.eql(u8, property, "isHydrated")) return 0; + } + + if (JsApi == @import("../webapi/Console.zig")) { + if (std.mem.eql(u8, property, "firebug")) return 0; + } + const ignored = std.StaticStringMap(void).initComptime(.{}); if (!ignored.has(property)) { log.debug(.unknown_prop, "unknown object property", .{ diff --git a/src/browser/webapi/Navigator.zig b/src/browser/webapi/Navigator.zig index 185c9551..e5f37f47 100644 --- a/src/browser/webapi/Navigator.zig +++ b/src/browser/webapi/Navigator.zig @@ -102,6 +102,14 @@ pub fn getPlugins(self: *Navigator) *PluginArray { return &self._plugins; } +pub fn getDoNotTrack(_: *const Navigator) ?[]const u8 { + return null; +} + +pub fn getGlobalPrivacyControl(_: *const Navigator) bool { + return true; +} + pub fn registerProtocolHandler(_: *const Navigator, scheme: []const u8, url: [:0]const u8, page: *const Page) !void { try validateProtocolHandlerScheme(scheme); try validateProtocolHandlerURL(url, page); @@ -196,6 +204,8 @@ pub const JsApi = struct { pub const product = bridge.accessor(Navigator.getProduct, null, .{}); pub const webdriver = bridge.accessor(Navigator.getWebdriver, null, .{}); pub const plugins = bridge.accessor(Navigator.getPlugins, null, .{}); + pub const doNotTrack = bridge.accessor(Navigator.getDoNotTrack, null, .{}); + pub const globalPrivacyControl = bridge.accessor(Navigator.getGlobalPrivacyControl, null, .{}); pub const registerProtocolHandler = bridge.function(Navigator.registerProtocolHandler, .{ .dom_exception = true }); pub const unregisterProtocolHandler = bridge.function(Navigator.unregisterProtocolHandler, .{ .dom_exception = true }); diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index 10a37ce7..c49817e9 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -100,6 +100,18 @@ pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void { try self.asElement().setAttributeSafe(comptime .wrap("loading"), .wrap(value), page); } +pub fn getNaturalWidth(_: *const Image) u32 { + // this is a valid response under a number of normal conditions, but could + // be used to detect the nature of Browser. + return 0; +} + +pub fn getNaturalHeight(_: *const Image) u32 { + // this is a valid response under a number of normal conditions, but could + // be used to detect the nature of Browser. + return 0; +} + pub const JsApi = struct { pub const bridge = js.Bridge(Image); @@ -117,6 +129,8 @@ pub const JsApi = struct { pub const height = bridge.accessor(Image.getHeight, Image.setHeight, .{}); pub const crossOrigin = bridge.accessor(Image.getCrossOrigin, Image.setCrossOrigin, .{}); pub const loading = bridge.accessor(Image.getLoading, Image.setLoading, .{}); + pub const naturalWidth = bridge.accessor(Image.getNaturalWidth, null, .{}); + pub const naturalHeight = bridge.accessor(Image.getNaturalHeight, null, .{}); }; pub const Build = struct { diff --git a/src/browser/webapi/element/html/Link.zig b/src/browser/webapi/element/html/Link.zig index 6fae0152..2e215bfa 100644 --- a/src/browser/webapi/element/html/Link.zig +++ b/src/browser/webapi/element/html/Link.zig @@ -30,6 +30,9 @@ _proto: *HtmlElement, pub fn asElement(self: *Link) *Element { return self._proto._proto; } +pub fn asConstElement(self: *const Link) *const Element { + return self._proto._proto; +} pub fn asNode(self: *Link) *Node { return self.asElement().asNode(); } @@ -57,6 +60,14 @@ pub fn setRel(self: *Link, value: []const u8, page: *Page) !void { try self.asElement().setAttributeSafe(comptime .wrap("rel"), .wrap(value), page); } +pub fn getAs(self: *const Link) []const u8 { + return self.asConstElement().getAttributeSafe(comptime .wrap("as")) orelse ""; +} + +pub fn setAs(self: *Link, value: []const u8, page: *Page) !void { + return self.asElement().setAttributeSafe(comptime .wrap("as"), .wrap(value), page); +} + pub const JsApi = struct { pub const bridge = js.Bridge(Link); @@ -66,6 +77,7 @@ pub const JsApi = struct { pub var class_id: bridge.ClassId = undefined; }; + pub const as = bridge.accessor(Link.getAs, Link.setAs, .{}); pub const rel = bridge.accessor(Link.getRel, Link.setRel, .{}); pub const href = bridge.accessor(Link.getHref, Link.setHref, .{}); pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true }); diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig index 771c9783..92b37199 100644 --- a/src/browser/webapi/element/html/Script.zig +++ b/src/browser/webapi/element/html/Script.zig @@ -76,6 +76,38 @@ pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void { return self.asElement().setAttributeSafe(comptime .wrap("nonce"), .wrap(value), page); } +pub fn getCharset(self: *const Script) []const u8 { + return self.asConstElement().getAttributeSafe(comptime .wrap("charset")) orelse ""; +} + +pub fn setCharset(self: *Script, value: []const u8, page: *Page) !void { + return self.asElement().setAttributeSafe(comptime .wrap("charset"), .wrap(value), page); +} + +pub fn getAsync(self: *const Script) bool { + return self.asConstElement().getAttributeSafe(comptime .wrap("async")) != null; +} + +pub fn setAsync(self: *Script, value: bool, page: *Page) !void { + if (value) { + try self.asElement().setAttributeSafe(comptime .wrap("async"), .wrap(""), page); + } else { + try self.asElement().removeAttribute(comptime .wrap("async"), page); + } +} + +pub fn getDefer(self: *const Script) bool { + return self.asConstElement().getAttributeSafe(comptime .wrap("defer")) != null; +} + +pub fn setDefer(self: *Script, value: bool, page: *Page) !void { + if (value) { + try self.asElement().setAttributeSafe(comptime .wrap("defer"), .wrap(""), page); + } else { + try self.asElement().removeAttribute(comptime .wrap("defer"), page); + } +} + pub fn getOnLoad(self: *const Script) ?js.Function.Global { return self._on_load; } @@ -110,8 +142,11 @@ pub const JsApi = struct { }; pub const src = bridge.accessor(Script.getSrc, Script.setSrc, .{}); + pub const @"defer" = bridge.accessor(Script.getDefer, Script.setDefer, .{}); + pub const async = bridge.accessor(Script.getAsync, Script.setAsync, .{}); pub const @"type" = bridge.accessor(Script.getType, Script.setType, .{}); pub const nonce = bridge.accessor(Script.getNonce, Script.setNonce, .{}); + pub const charset = bridge.accessor(Script.getCharset, Script.setCharset, .{}); pub const onload = bridge.accessor(Script.getOnLoad, Script.setOnLoad, .{}); pub const onerror = bridge.accessor(Script.getOnError, Script.setOnError, .{}); pub const noModule = bridge.accessor(Script.getNoModule, null, .{});