From d26869278fa0a926cad830e741e872a70d1c0834 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 16 Dec 2025 11:13:57 +0800 Subject: [PATCH] dummy HTMLCanvasElement --- src/browser/Page.zig | 6 + src/browser/js/Context.zig | 12 +- src/browser/js/bridge.zig | 1 + src/browser/tests/legacy/html/canvas.html | 16 -- src/browser/webapi/Element.zig | 4 + src/browser/webapi/element/Html.zig | 3 + src/browser/webapi/element/html/Canvas.zig | 225 +++++++++++++++++++++ 7 files changed, 250 insertions(+), 17 deletions(-) create mode 100644 src/browser/webapi/element/html/Canvas.zig diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 0fd81e81..2c2d7710 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1243,6 +1243,12 @@ pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_ attribute_iterator, .{ ._proto = undefined }, ), + asUint("canvas") => return self.createHtmlElementT( + Element.Html.Canvas, + namespace, + attribute_iterator, + .{ ._proto = undefined }, + ), asUint("dialog") => return self.createHtmlElementT( Element.Html.Dialog, namespace, diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 63a6d751..dc297b21 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -1122,8 +1122,18 @@ fn _debugValue(self: *const Context, js_val: v8.Value, seen: *std.AutoHashMapUnm if (depth > 20) { return writer.writeAll("...deeply nested object..."); } + const own_len = js_obj.getOwnPropertyNames(v8_context).length(); + if (own_len == 0) { + const js_val_str = try self.valueToString(js_val, .{}); + if (js_val_str.len > 2000) { + try writer.writeAll(js_val_str[0..2000]); + return writer.writeAll(" ... (truncated)"); + } + return writer.writeAll(js_val_str); + } - try writer.print("({d}/{d})", .{ js_obj.getOwnPropertyNames(v8_context).length(), js_obj.getPropertyNames(v8_context).length() }); + const all_len = js_obj.getPropertyNames(v8_context).length(); + try writer.print("({d}/{d})", .{ own_len, all_len }); for (0..len) |i| { if (i == 0) { try writer.writeByte('\n'); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 4059d6a8..d379ebf2 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -530,6 +530,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/element/html/Body.zig"), @import("../webapi/element/html/BR.zig"), @import("../webapi/element/html/Button.zig"), + @import("../webapi/element/html/Canvas.zig"), @import("../webapi/element/html/Custom.zig"), @import("../webapi/element/html/Data.zig"), @import("../webapi/element/html/Dialog.zig"), diff --git a/src/browser/tests/legacy/html/canvas.html b/src/browser/tests/legacy/html/canvas.html index ab076487..ed1980eb 100644 --- a/src/browser/tests/legacy/html/canvas.html +++ b/src/browser/tests/legacy/html/canvas.html @@ -11,19 +11,3 @@ } - diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 97a2ecb4..c47896eb 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -172,6 +172,7 @@ pub fn getTagNameLower(self: *const Element) []const u8 { .body => "body", .br => "br", .button => "button", + .canvas => "canvas", .custom => |e| e._tag_name.str(), .data => "data", .dialog => "dialog", @@ -224,6 +225,7 @@ pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 { .body => "BODY", .br => "BR", .button => "BUTTON", + .canvas => "CANVAS", .custom => |e| upperTagName(&e._tag_name, buf), .data => "DATA", .dialog => "DIALOG", @@ -1082,6 +1084,7 @@ pub fn getTag(self: *const Element) Tag { .img => .img, .br => .br, .button => .button, + .canvas => .canvas, .heading => |h| h._tag, .li => .li, .ul => .ul, @@ -1123,6 +1126,7 @@ pub const Tag = enum { body, br, button, + canvas, circle, custom, data, diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index fe88e96c..8201a68e 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -27,6 +27,7 @@ pub const Anchor = @import("html/Anchor.zig"); pub const Body = @import("html/Body.zig"); pub const BR = @import("html/BR.zig"); pub const Button = @import("html/Button.zig"); +pub const Canvas = @import("html/Canvas.zig"); pub const Custom = @import("html/Custom.zig"); pub const Data = @import("html/Data.zig"); pub const Dialog = @import("html/Dialog.zig"); @@ -74,6 +75,7 @@ pub const Type = union(enum) { body: *Body, br: *BR, button: *Button, + canvas: *Canvas, custom: *Custom, data: *Data, dialog: *Dialog, @@ -126,6 +128,7 @@ pub fn className(self: *const HtmlElement) []const u8 { .body => "[object HTMLBodyElement]", .br => "[object HTMLBRElement]", .button => "[object HTMLButtonElement]", + .canvas => "[object HTMLCanvasElement]", .custom => "[object CUSTOM-TODO]", .data => "[object HTMLDataElement]", .dialog => "[object HTMLDialogElement]", diff --git a/src/browser/webapi/element/html/Canvas.zig b/src/browser/webapi/element/html/Canvas.zig new file mode 100644 index 00000000..eb60befd --- /dev/null +++ b/src/browser/webapi/element/html/Canvas.zig @@ -0,0 +1,225 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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 &.{ + Canvas, + RenderingContext2D, + }; +} + +const Canvas = @This(); +_proto: *HtmlElement, + +pub const RenderingContext2D = struct { + pub fn save(_: *RenderingContext2D) void {} + pub fn restore(_: *RenderingContext2D) void {} + + pub fn scale(_: *RenderingContext2D, _: f64, _: f64) void {} + pub fn rotate(_: *RenderingContext2D, _: f64) void {} + pub fn translate(_: *RenderingContext2D, _: f64, _: f64) void {} + pub fn transform(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: f64) void {} + pub fn setTransform(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: f64) void {} + pub fn resetTransform(_: *RenderingContext2D) void {} + + pub fn getGlobalAlpha(_: *const RenderingContext2D) f64 { + return 1.0; + } + pub fn setGlobalAlpha(_: *RenderingContext2D, _: f64) void {} + pub fn getGlobalCompositeOperation(_: *const RenderingContext2D) []const u8 { + return "source-over"; + } + pub fn setGlobalCompositeOperation(_: *RenderingContext2D, _: []const u8) void {} + + pub fn getFillStyle(_: *const RenderingContext2D) []const u8 { + return "#000000"; + } + pub fn setFillStyle(_: *RenderingContext2D, _: []const u8) void {} + pub fn getStrokeStyle(_: *const RenderingContext2D) []const u8 { + return "#000000"; + } + pub fn setStrokeStyle(_: *RenderingContext2D, _: []const u8) void {} + + pub fn getLineWidth(_: *const RenderingContext2D) f64 { + return 1.0; + } + pub fn setLineWidth(_: *RenderingContext2D, _: f64) void {} + pub fn getLineCap(_: *const RenderingContext2D) []const u8 { + return "butt"; + } + pub fn setLineCap(_: *RenderingContext2D, _: []const u8) void {} + pub fn getLineJoin(_: *const RenderingContext2D) []const u8 { + return "miter"; + } + pub fn setLineJoin(_: *RenderingContext2D, _: []const u8) void {} + pub fn getMiterLimit(_: *const RenderingContext2D) f64 { + return 10.0; + } + pub fn setMiterLimit(_: *RenderingContext2D, _: f64) void {} + + pub fn clearRect(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {} + pub fn fillRect(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {} + pub fn strokeRect(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {} + + pub fn beginPath(_: *RenderingContext2D) void {} + pub fn closePath(_: *RenderingContext2D) void {} + pub fn moveTo(_: *RenderingContext2D, _: f64, _: f64) void {} + pub fn lineTo(_: *RenderingContext2D, _: f64, _: f64) void {} + pub fn quadraticCurveTo(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {} + pub fn bezierCurveTo(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: f64) void {} + pub fn arc(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: ?bool) void {} + pub fn arcTo(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64) void {} + pub fn rect(_: *RenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {} + + pub fn fill(_: *RenderingContext2D) void {} + pub fn stroke(_: *RenderingContext2D) void {} + pub fn clip(_: *RenderingContext2D) void {} + + pub fn getFont(_: *const RenderingContext2D) []const u8 { + return "10px sans-serif"; + } + pub fn setFont(_: *RenderingContext2D, _: []const u8) void {} + pub fn getTextAlign(_: *const RenderingContext2D) []const u8 { + return "start"; + } + pub fn setTextAlign(_: *RenderingContext2D, _: []const u8) void {} + pub fn getTextBaseline(_: *const RenderingContext2D) []const u8 { + return "alphabetic"; + } + pub fn setTextBaseline(_: *RenderingContext2D, _: []const u8) void {} + pub fn fillText(_: *RenderingContext2D, _: []const u8, _: f64, _: f64, _: ?f64) void {} + pub fn strokeText(_: *RenderingContext2D, _: []const u8, _: f64, _: f64, _: ?f64) void {} + + pub const JsApi = struct { + pub const bridge = js.Bridge(RenderingContext2D); + + pub const Meta = struct { + pub const name = "CanvasRenderingContext2D"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const save = bridge.function(RenderingContext2D.save, .{}); + pub const restore = bridge.function(RenderingContext2D.restore, .{}); + + pub const scale = bridge.function(RenderingContext2D.scale, .{}); + pub const rotate = bridge.function(RenderingContext2D.rotate, .{}); + pub const translate = bridge.function(RenderingContext2D.translate, .{}); + pub const transform = bridge.function(RenderingContext2D.transform, .{}); + pub const setTransform = bridge.function(RenderingContext2D.setTransform, .{}); + pub const resetTransform = bridge.function(RenderingContext2D.resetTransform, .{}); + + pub const globalAlpha = bridge.accessor(RenderingContext2D.getGlobalAlpha, RenderingContext2D.setGlobalAlpha, .{}); + pub const globalCompositeOperation = bridge.accessor(RenderingContext2D.getGlobalCompositeOperation, RenderingContext2D.setGlobalCompositeOperation, .{}); + + pub const fillStyle = bridge.accessor(RenderingContext2D.getFillStyle, RenderingContext2D.setFillStyle, .{}); + pub const strokeStyle = bridge.accessor(RenderingContext2D.getStrokeStyle, RenderingContext2D.setStrokeStyle, .{}); + + pub const lineWidth = bridge.accessor(RenderingContext2D.getLineWidth, RenderingContext2D.setLineWidth, .{}); + pub const lineCap = bridge.accessor(RenderingContext2D.getLineCap, RenderingContext2D.setLineCap, .{}); + pub const lineJoin = bridge.accessor(RenderingContext2D.getLineJoin, RenderingContext2D.setLineJoin, .{}); + pub const miterLimit = bridge.accessor(RenderingContext2D.getMiterLimit, RenderingContext2D.setMiterLimit, .{}); + + pub const clearRect = bridge.function(RenderingContext2D.clearRect, .{}); + pub const fillRect = bridge.function(RenderingContext2D.fillRect, .{}); + pub const strokeRect = bridge.function(RenderingContext2D.strokeRect, .{}); + + pub const beginPath = bridge.function(RenderingContext2D.beginPath, .{}); + pub const closePath = bridge.function(RenderingContext2D.closePath, .{}); + pub const moveTo = bridge.function(RenderingContext2D.moveTo, .{}); + pub const lineTo = bridge.function(RenderingContext2D.lineTo, .{}); + pub const quadraticCurveTo = bridge.function(RenderingContext2D.quadraticCurveTo, .{}); + pub const bezierCurveTo = bridge.function(RenderingContext2D.bezierCurveTo, .{}); + pub const arc = bridge.function(RenderingContext2D.arc, .{}); + pub const arcTo = bridge.function(RenderingContext2D.arcTo, .{}); + pub const rect = bridge.function(RenderingContext2D.rect, .{}); + + pub const fill = bridge.function(RenderingContext2D.fill, .{}); + pub const stroke = bridge.function(RenderingContext2D.stroke, .{}); + pub const clip = bridge.function(RenderingContext2D.clip, .{}); + + pub const font = bridge.accessor(RenderingContext2D.getFont, RenderingContext2D.setFont, .{}); + pub const textAlign = bridge.accessor(RenderingContext2D.getTextAlign, RenderingContext2D.setTextAlign, .{}); + pub const textBaseline = bridge.accessor(RenderingContext2D.getTextBaseline, RenderingContext2D.setTextBaseline, .{}); + pub const fillText = bridge.function(RenderingContext2D.fillText, .{}); + pub const strokeText = bridge.function(RenderingContext2D.strokeText, .{}); + }; +}; + +pub fn asElement(self: *Canvas) *Element { + return self._proto._proto; +} +pub fn asConstElement(self: *const Canvas) *const Element { + return self._proto._proto; +} +pub fn asNode(self: *Canvas) *Node { + return self.asElement().asNode(); +} + +pub fn getWidth(self: *const Canvas) u32 { + const attr = self.asConstElement().getAttributeSafe("width") orelse return 300; + return std.fmt.parseUnsigned(u32, attr, 10) catch 300; +} + +pub fn setWidth(self: *Canvas, 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 Canvas) u32 { + const attr = self.asConstElement().getAttributeSafe("height") orelse return 150; + return std.fmt.parseUnsigned(u32, attr, 10) catch 150; +} + +pub fn setHeight(self: *Canvas, 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 getContext(self: *Canvas, context_type: []const u8, page: *Page) !?*RenderingContext2D { + _ = self; + + if (!std.mem.eql(u8, context_type, "2d")) { + return null; + } + + const ctx = try page.arena.create(RenderingContext2D); + ctx.* = .{}; + return ctx; +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Canvas); + + pub const Meta = struct { + pub const name = "HTMLCanvasElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const width = bridge.accessor(Canvas.getWidth, Canvas.setWidth, .{}); + pub const height = bridge.accessor(Canvas.getHeight, Canvas.setHeight, .{}); + pub const getContext = bridge.function(Canvas.getContext, .{}); +};