Support Image constructor (i.e. new Image(..))

This commit is contained in:
Karl Seguin
2025-11-27 18:16:03 +08:00
parent f25b8fc7b0
commit 819424fd3b
4 changed files with 188 additions and 33 deletions

View File

@@ -333,19 +333,6 @@ fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTem
return template;
}
// fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
// const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
// if (has_js_call_as_function) {
// if (@hasDecl(Struct, "htmldda") and Struct.htmldda) {
// if (!has_js_call_as_function) {
// @compileError(@typeName(Struct) ++ ": htmldda required jsCallAsFunction to be defined. This is a hard-coded requirement in V8, because mark_as_undetectable only exists for HTMLAllCollection which is also callable.");
// }
// template.markAsUndetectable();
// }
// }
pub fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt {
@setEvalBranchQuota(2000);
comptime {

View File

@@ -116,8 +116,9 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal
// are now going to get associated with our global instance.
inline for (JsApis, 0..) |JsApi, i| {
if (@hasDecl(JsApi.Meta, "name")) {
const class_name = v8.String.initUtf8(isolate, JsApi.Meta.name);
global_template.set(class_name.toName(), templates[i], v8.PropertyAttribute.None);
const class_name = if (@hasDecl(JsApi.Meta, "constructor_alias")) JsApi.Meta.constructor_alias else JsApi.Meta.name;
const v8_class_name = v8.String.initUtf8(isolate, class_name);
global_template.set(v8_class_name.toName(), templates[i], v8.PropertyAttribute.None);
}
}

View File

@@ -0,0 +1,89 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<script id="Image">
{
let i1 = new Image()
testing.expectEqual(0, i1.width);
testing.expectEqual(null, i1.getAttribute('width'));
testing.expectEqual(0, i1.height);
testing.expectEqual(null, i1.getAttribute('height'));
let i2 = new Image(10)
testing.expectEqual(10, i2.width);
testing.expectEqual('10', i2.getAttribute('width'));
testing.expectEqual(0, i2.height);
testing.expectEqual(null, i2.getAttribute('height'));
let i3 = new Image(10, 20)
testing.expectEqual(10, i3.width);
testing.expectEqual('10', i3.getAttribute('width'));
testing.expectEqual(20, i3.height);
testing.expectEqual('20', i3.getAttribute('height'));
}
</script>
<script id="src_alt">
{
const img = document.createElement('img');
testing.expectEqual('', img.src);
testing.expectEqual('', img.alt);
img.src = 'test.png';
testing.expectEqual('test.png', img.src);
testing.expectEqual('test.png', img.getAttribute('src'));
img.alt = 'Test image';
testing.expectEqual('Test image', img.alt);
testing.expectEqual('Test image', img.getAttribute('alt'));
}
</script>
<script id="width_height_setters">
{
const img = document.createElement('img');
img.width = 100;
testing.expectEqual(100, img.width);
testing.expectEqual('100', img.getAttribute('width'));
img.height = 200;
testing.expectEqual(200, img.height);
testing.expectEqual('200', img.getAttribute('height'));
img.setAttribute('width', '50');
testing.expectEqual(50, img.width);
img.setAttribute('height', 'invalid');
testing.expectEqual(0, img.height);
}
</script>
<script id="crossOrigin">
{
const img = document.createElement('img');
testing.expectEqual(null, img.crossOrigin);
img.crossOrigin = 'anonymous';
testing.expectEqual('anonymous', img.crossOrigin);
testing.expectEqual('anonymous', img.getAttribute('crossorigin'));
img.crossOrigin = null;
testing.expectEqual(null, img.crossOrigin);
testing.expectEqual(null, img.getAttribute('crossorigin'));
}
</script>
<script id="loading">
{
const img = document.createElement('img');
testing.expectEqual('eager', img.loading);
img.loading = 'lazy';
testing.expectEqual('lazy', img.loading);
testing.expectEqual('lazy', img.getAttribute('loading'));
}
</script>

View File

@@ -1,42 +1,120 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
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");
pub fn registerTypes() []const type {
return &.{
Image,
// Factory,
};
}
const Image = @This();
_proto: *HtmlElement,
pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image {
const node = try page.createElement(null, "img", null);
const el = node.as(Element);
if (w_) |w| blk: {
const w_string = std.fmt.bufPrint(&page.buf, "{d}", .{w}) catch break :blk;
try el.setAttributeSafe("width", w_string, page);
}
if (h_) |h| blk: {
const h_string = std.fmt.bufPrint(&page.buf, "{d}", .{h}) catch break :blk;
try el.setAttributeSafe("height", h_string, page);
}
return el.as(Image);
}
pub fn asElement(self: *Image) *Element {
return self._proto._proto;
}
pub fn asConstElement(self: *const Image) *const Element {
return self._proto._proto;
}
pub fn asNode(self: *Image) *Node {
return self.asElement().asNode();
}
pub fn getSrc(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("src") orelse "";
}
pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("src", value, page);
}
pub fn getAlt(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("alt") orelse "";
}
pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("alt", value, page);
}
pub fn getWidth(self: *const Image) u32 {
const attr = self.asConstElement().getAttributeSafe("width") orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
}
pub fn setWidth(self: *Image, value: u32, page: *Page) !void {
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
try self.asElement().setAttributeSafe("width", str, page);
}
pub fn getHeight(self: *const Image) u32 {
const attr = self.asConstElement().getAttributeSafe("height") orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
}
pub fn setHeight(self: *Image, value: u32, page: *Page) !void {
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value});
try self.asElement().setAttributeSafe("height", str, page);
}
pub fn getCrossOrigin(self: *const Image) ?[]const u8 {
return self.asConstElement().getAttributeSafe("crossorigin");
}
pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void {
if (value) |v| {
return self.asElement().setAttributeSafe("crossorigin", v, page);
}
return self.asElement().removeAttribute("crossorigin", page);
}
pub fn getLoading(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("loading") orelse "eager";
}
pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("loading", value, page);
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Image);
pub const Meta = struct {
pub const name = "HTMLImageElement";
pub const constructor_alias = "Image";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const constructor = bridge.constructor(Image.constructor, .{});
pub const src = bridge.accessor(Image.getSrc, Image.setSrc, .{});
pub const alt = bridge.accessor(Image.getAlt, Image.setAlt, .{});
pub const width = bridge.accessor(Image.getWidth, Image.setWidth, .{});
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, .{});
};
const testing = @import("../../../../testing.zig");
test "WebApi: HTML.Image" {
try testing.htmlRunner("element/html/image.html", .{});
}