mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge branch 'main' into wpt-fixes-batch-3
This commit is contained in:
@@ -2490,13 +2490,16 @@ pub fn createCDATASection(self: *Page, data: []const u8) !*Node {
|
||||
}
|
||||
|
||||
pub fn createProcessingInstruction(self: *Page, target: []const u8, data: []const u8) !*Node {
|
||||
// Validate target doesn't contain "?>"
|
||||
// Validate neither target nor data contain "?>"
|
||||
if (std.mem.indexOf(u8, target, "?>") != null) {
|
||||
return error.InvalidCharacterError;
|
||||
}
|
||||
if (std.mem.indexOf(u8, data, "?>") != null) {
|
||||
return error.InvalidCharacterError;
|
||||
}
|
||||
|
||||
// Validate target follows XML name rules (similar to attribute name validation)
|
||||
try Element.Attribute.validateAttributeName(.wrap(target));
|
||||
// Validate target follows XML Name production
|
||||
try validateXmlName(target);
|
||||
|
||||
const owned_target = try self.dupeString(target);
|
||||
const owned_data = try self.dupeString(data);
|
||||
@@ -2518,6 +2521,63 @@ pub fn createProcessingInstruction(self: *Page, target: []const u8, data: []cons
|
||||
return cd.asNode();
|
||||
}
|
||||
|
||||
/// Validate a string against the XML Name production.
|
||||
/// https://www.w3.org/TR/xml/#NT-Name
|
||||
fn validateXmlName(name: []const u8) !void {
|
||||
if (name.len == 0) return error.InvalidCharacterError;
|
||||
|
||||
var i: usize = 0;
|
||||
|
||||
// First character must be a NameStartChar.
|
||||
const first_len = std.unicode.utf8ByteSequenceLength(name[0]) catch
|
||||
return error.InvalidCharacterError;
|
||||
if (first_len > name.len) return error.InvalidCharacterError;
|
||||
const first_cp = std.unicode.utf8Decode(name[0..][0..first_len]) catch
|
||||
return error.InvalidCharacterError;
|
||||
if (!isXmlNameStartChar(first_cp)) return error.InvalidCharacterError;
|
||||
i = first_len;
|
||||
|
||||
// Subsequent characters must be NameChars.
|
||||
while (i < name.len) {
|
||||
const cp_len = std.unicode.utf8ByteSequenceLength(name[i]) catch
|
||||
return error.InvalidCharacterError;
|
||||
if (i + cp_len > name.len) return error.InvalidCharacterError;
|
||||
const cp = std.unicode.utf8Decode(name[i..][0..cp_len]) catch
|
||||
return error.InvalidCharacterError;
|
||||
if (!isXmlNameChar(cp)) return error.InvalidCharacterError;
|
||||
i += cp_len;
|
||||
}
|
||||
}
|
||||
|
||||
fn isXmlNameStartChar(c: u21) bool {
|
||||
return c == ':' or
|
||||
(c >= 'A' and c <= 'Z') or
|
||||
c == '_' or
|
||||
(c >= 'a' and c <= 'z') or
|
||||
(c >= 0xC0 and c <= 0xD6) or
|
||||
(c >= 0xD8 and c <= 0xF6) or
|
||||
(c >= 0xF8 and c <= 0x2FF) or
|
||||
(c >= 0x370 and c <= 0x37D) or
|
||||
(c >= 0x37F and c <= 0x1FFF) or
|
||||
(c >= 0x200C and c <= 0x200D) or
|
||||
(c >= 0x2070 and c <= 0x218F) or
|
||||
(c >= 0x2C00 and c <= 0x2FEF) or
|
||||
(c >= 0x3001 and c <= 0xD7FF) or
|
||||
(c >= 0xF900 and c <= 0xFDCF) or
|
||||
(c >= 0xFDF0 and c <= 0xFFFD) or
|
||||
(c >= 0x10000 and c <= 0xEFFFF);
|
||||
}
|
||||
|
||||
fn isXmlNameChar(c: u21) bool {
|
||||
return isXmlNameStartChar(c) or
|
||||
c == '-' or
|
||||
c == '.' or
|
||||
(c >= '0' and c <= '9') or
|
||||
c == 0xB7 or
|
||||
(c >= 0x300 and c <= 0x36F) or
|
||||
(c >= 0x203F and c <= 0x2040);
|
||||
}
|
||||
|
||||
pub fn dupeString(self: *Page, value: []const u8) ![]const u8 {
|
||||
if (String.intern(value)) |v| {
|
||||
return v;
|
||||
|
||||
@@ -713,6 +713,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/css/CSSStyleRule.zig"),
|
||||
@import("../webapi/css/CSSStyleSheet.zig"),
|
||||
@import("../webapi/css/CSSStyleProperties.zig"),
|
||||
@import("../webapi/css/FontFaceSet.zig"),
|
||||
@import("../webapi/css/MediaQueryList.zig"),
|
||||
@import("../webapi/css/StyleSheetList.zig"),
|
||||
@import("../webapi/Document.zig"),
|
||||
@@ -865,6 +866,8 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/navigation/NavigationActivation.zig"),
|
||||
@import("../webapi/canvas/CanvasRenderingContext2D.zig"),
|
||||
@import("../webapi/canvas/WebGLRenderingContext.zig"),
|
||||
@import("../webapi/canvas/OffscreenCanvas.zig"),
|
||||
@import("../webapi/canvas/OffscreenCanvasRenderingContext2D.zig"),
|
||||
@import("../webapi/SubtleCrypto.zig"),
|
||||
@import("../webapi/Selection.zig"),
|
||||
@import("../webapi/ImageData.zig"),
|
||||
|
||||
64
src/browser/tests/canvas/offscreen_canvas.html
Normal file
64
src/browser/tests/canvas/offscreen_canvas.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=OffscreenCanvas>
|
||||
{
|
||||
const canvas = new OffscreenCanvas(256, 256);
|
||||
testing.expectEqual(true, canvas instanceof OffscreenCanvas);
|
||||
testing.expectEqual(canvas.width, 256);
|
||||
testing.expectEqual(canvas.height, 256);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=OffscreenCanvas#width>
|
||||
{
|
||||
const canvas = new OffscreenCanvas(100, 200);
|
||||
testing.expectEqual(canvas.width, 100);
|
||||
canvas.width = 300;
|
||||
testing.expectEqual(canvas.width, 300);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=OffscreenCanvas#height>
|
||||
{
|
||||
const canvas = new OffscreenCanvas(100, 200);
|
||||
testing.expectEqual(canvas.height, 200);
|
||||
canvas.height = 400;
|
||||
testing.expectEqual(canvas.height, 400);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=OffscreenCanvas#getContext>
|
||||
{
|
||||
const canvas = new OffscreenCanvas(64, 64);
|
||||
const ctx = canvas.getContext("2d");
|
||||
testing.expectEqual(true, ctx instanceof OffscreenCanvasRenderingContext2D);
|
||||
// We can't really test rendering but let's try to call it at least.
|
||||
ctx.fillRect(0, 0, 10, 10);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=OffscreenCanvas#convertToBlob>
|
||||
{
|
||||
const canvas = new OffscreenCanvas(64, 64);
|
||||
const promise = canvas.convertToBlob();
|
||||
testing.expectEqual(true, promise instanceof Promise);
|
||||
// The promise should resolve to a Blob (even if empty)
|
||||
promise.then(blob => {
|
||||
testing.expectEqual(true, blob instanceof Blob);
|
||||
testing.expectEqual(blob.size, 0); // Empty since no rendering
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=HTMLCanvasElement#transferControlToOffscreen>
|
||||
{
|
||||
const htmlCanvas = document.createElement("canvas");
|
||||
htmlCanvas.width = 128;
|
||||
htmlCanvas.height = 96;
|
||||
const offscreen = htmlCanvas.transferControlToOffscreen();
|
||||
testing.expectEqual(true, offscreen instanceof OffscreenCanvas);
|
||||
testing.expectEqual(offscreen.width, 128);
|
||||
testing.expectEqual(offscreen.height, 96);
|
||||
}
|
||||
</script>
|
||||
58
src/browser/tests/css/font_face_set.html
Normal file
58
src/browser/tests/css/font_face_set.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id="document_fonts_exists">
|
||||
{
|
||||
testing.expectTrue(document.fonts !== undefined);
|
||||
testing.expectTrue(document.fonts !== null);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_same_instance">
|
||||
{
|
||||
// Should return same instance each time
|
||||
const f1 = document.fonts;
|
||||
const f2 = document.fonts;
|
||||
testing.expectTrue(f1 === f2);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_status">
|
||||
{
|
||||
testing.expectEqual('loaded', document.fonts.status);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_size">
|
||||
{
|
||||
testing.expectEqual(0, document.fonts.size);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_ready_is_promise">
|
||||
{
|
||||
const ready = document.fonts.ready;
|
||||
testing.expectTrue(ready instanceof Promise);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_ready_resolves">
|
||||
{
|
||||
let resolved = false;
|
||||
document.fonts.ready.then(() => { resolved = true; });
|
||||
// Promise resolution is async; just confirm .then() does not throw
|
||||
testing.expectTrue(typeof document.fonts.ready.then === 'function');
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_check">
|
||||
{
|
||||
testing.expectTrue(document.fonts.check('16px sans-serif'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="document_fonts_constructor_name">
|
||||
{
|
||||
testing.expectEqual('FontFaceSet', document.fonts.constructor.name);
|
||||
}
|
||||
</script>
|
||||
@@ -46,7 +46,7 @@
|
||||
testing.expectEqual(5, input.maxLength);
|
||||
input.maxLength = 'banana';
|
||||
testing.expectEqual(0, input.maxLength);
|
||||
testing.expectError('Error: NegativeValueNotAllowed', () => { input.maxLength = -45;});
|
||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => { input.maxLength = -45;});
|
||||
|
||||
testing.expectEqual(20, input.size);
|
||||
input.size = 5;
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
testing.expectEqual('Text 3', $('#opt3').text)
|
||||
</script>
|
||||
|
||||
<script id="text_set">
|
||||
$('#opt1').text = 'New Text 1'
|
||||
testing.expectEqual('New Text 1', $('#opt1').text)
|
||||
testing.expectEqual('New Text 1', $('#opt1').textContent)
|
||||
</script>
|
||||
|
||||
<script id="selected">
|
||||
testing.expectEqual(false, $('#opt1').selected)
|
||||
testing.expectEqual(true, $('#opt2').selected)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
testing.expectEqual(5, input.maxLength);
|
||||
input.maxLength = 'banana';
|
||||
testing.expectEqual(0, input.maxLength);
|
||||
testing.expectError('Error: NegativeValueNotAllowed', () => { input.maxLength = -45;});
|
||||
testing.expectError('IndexSizeError: Index or size is negative or greater than the allowed amount', () => { input.maxLength = -45;});
|
||||
|
||||
testing.expectEqual(20, input.size);
|
||||
input.size = 5;
|
||||
|
||||
@@ -34,6 +34,7 @@ const DOMTreeWalker = @import("DOMTreeWalker.zig");
|
||||
const DOMNodeIterator = @import("DOMNodeIterator.zig");
|
||||
const DOMImplementation = @import("DOMImplementation.zig");
|
||||
const StyleSheetList = @import("css/StyleSheetList.zig");
|
||||
const FontFaceSet = @import("css/FontFaceSet.zig");
|
||||
const Selection = @import("Selection.zig");
|
||||
|
||||
pub const XMLDocument = @import("XMLDocument.zig");
|
||||
@@ -53,6 +54,7 @@ _removed_ids: std.StringHashMapUnmanaged(void) = .empty,
|
||||
_active_element: ?*Element = null,
|
||||
_style_sheets: ?*StyleSheetList = null,
|
||||
_implementation: ?*DOMImplementation = null,
|
||||
_fonts: ?*FontFaceSet = null,
|
||||
_write_insertion_point: ?*Node = null,
|
||||
_script_created_parser: ?Parser.Streaming = null,
|
||||
_adopted_style_sheets: ?js.Object.Global = null,
|
||||
@@ -434,6 +436,15 @@ pub fn getStyleSheets(self: *Document, page: *Page) !*StyleSheetList {
|
||||
return sheets;
|
||||
}
|
||||
|
||||
pub fn getFonts(self: *Document, page: *Page) !*FontFaceSet {
|
||||
if (self._fonts) |fonts| {
|
||||
return fonts;
|
||||
}
|
||||
const fonts = try FontFaceSet.init(page);
|
||||
self._fonts = fonts;
|
||||
return fonts;
|
||||
}
|
||||
|
||||
pub fn adoptNode(_: *const Document, node: *Node, page: *Page) !*Node {
|
||||
if (node._type == .document) {
|
||||
return error.NotSupported;
|
||||
@@ -968,6 +979,7 @@ pub const JsApi = struct {
|
||||
pub const implementation = bridge.accessor(Document.getImplementation, null, .{});
|
||||
pub const activeElement = bridge.accessor(Document.getActiveElement, null, .{});
|
||||
pub const styleSheets = bridge.accessor(Document.getStyleSheets, null, .{});
|
||||
pub const fonts = bridge.accessor(Document.getFonts, null, .{});
|
||||
pub const contentType = bridge.accessor(Document.getContentType, null, .{});
|
||||
pub const domain = bridge.accessor(Document.getDomain, null, .{});
|
||||
pub const createElement = bridge.function(Document.createElement, .{ .dom_exception = true });
|
||||
|
||||
@@ -1374,13 +1374,6 @@ fn upperTagName(tag_name: *String, buf: []u8) []const u8 {
|
||||
return tag_name.str();
|
||||
}
|
||||
const tag = tag_name.str();
|
||||
// If the tag_name has a prefix, we must uppercase only the suffix part.
|
||||
// example: te:st should be returned as te:ST.
|
||||
if (std.mem.indexOfPos(u8, tag, 0, ":")) |pos| {
|
||||
@memcpy(buf[0 .. pos + 1], tag[0 .. pos + 1]);
|
||||
_ = std.ascii.upperString(buf[pos..tag.len], tag[pos..tag.len]);
|
||||
return buf[0..tag.len];
|
||||
}
|
||||
return std.ascii.upperString(buf, tag);
|
||||
}
|
||||
|
||||
|
||||
105
src/browser/webapi/canvas/OffscreenCanvas.zig
Normal file
105
src/browser/webapi/canvas/OffscreenCanvas.zig
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (C) 2023-2026 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 Blob = @import("../Blob.zig");
|
||||
const OffscreenCanvasRenderingContext2D = @import("OffscreenCanvasRenderingContext2D.zig");
|
||||
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
|
||||
const OffscreenCanvas = @This();
|
||||
|
||||
pub const _prototype_root = true;
|
||||
|
||||
_width: u32,
|
||||
_height: u32,
|
||||
|
||||
/// Since there's no base class rendering contextes inherit from,
|
||||
/// we're using tagged union.
|
||||
const DrawingContext = union(enum) {
|
||||
@"2d": *OffscreenCanvasRenderingContext2D,
|
||||
};
|
||||
|
||||
pub fn constructor(width: u32, height: u32, page: *Page) !*OffscreenCanvas {
|
||||
return page._factory.create(OffscreenCanvas{
|
||||
._width = width,
|
||||
._height = height,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getWidth(self: *const OffscreenCanvas) u32 {
|
||||
return self._width;
|
||||
}
|
||||
|
||||
pub fn setWidth(self: *OffscreenCanvas, value: u32) void {
|
||||
self._width = value;
|
||||
}
|
||||
|
||||
pub fn getHeight(self: *const OffscreenCanvas) u32 {
|
||||
return self._height;
|
||||
}
|
||||
|
||||
pub fn setHeight(self: *OffscreenCanvas, value: u32) void {
|
||||
self._height = value;
|
||||
}
|
||||
|
||||
pub fn getContext(_: *OffscreenCanvas, context_type: []const u8, page: *Page) !?DrawingContext {
|
||||
if (std.mem.eql(u8, context_type, "2d")) {
|
||||
const ctx = try page._factory.create(OffscreenCanvasRenderingContext2D{});
|
||||
return .{ .@"2d" = ctx };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns a Promise that resolves to a Blob containing the image.
|
||||
/// Since we have no actual rendering, this returns an empty blob.
|
||||
pub fn convertToBlob(_: *OffscreenCanvas, page: *Page) !js.Promise {
|
||||
const blob = try Blob.init(null, null, page);
|
||||
return page.js.local.?.resolvePromise(blob);
|
||||
}
|
||||
|
||||
/// Returns an ImageBitmap with the rendered content (stub).
|
||||
pub fn transferToImageBitmap(_: *OffscreenCanvas) ?void {
|
||||
// ImageBitmap not implemented yet, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(OffscreenCanvas);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "OffscreenCanvas";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(OffscreenCanvas.constructor, .{});
|
||||
pub const width = bridge.accessor(OffscreenCanvas.getWidth, OffscreenCanvas.setWidth, .{});
|
||||
pub const height = bridge.accessor(OffscreenCanvas.getHeight, OffscreenCanvas.setHeight, .{});
|
||||
pub const getContext = bridge.function(OffscreenCanvas.getContext, .{});
|
||||
pub const convertToBlob = bridge.function(OffscreenCanvas.convertToBlob, .{});
|
||||
pub const transferToImageBitmap = bridge.function(OffscreenCanvas.transferToImageBitmap, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: OffscreenCanvas" {
|
||||
try testing.htmlRunner("canvas/offscreen_canvas.html", .{});
|
||||
}
|
||||
209
src/browser/webapi/canvas/OffscreenCanvasRenderingContext2D.zig
Normal file
209
src/browser/webapi/canvas/OffscreenCanvasRenderingContext2D.zig
Normal file
@@ -0,0 +1,209 @@
|
||||
// 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 color = @import("../../color.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const ImageData = @import("../ImageData.zig");
|
||||
|
||||
/// This class doesn't implement a `constructor`.
|
||||
/// It can be obtained with a call to `OffscreenCanvas#getContext`.
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvasRenderingContext2D
|
||||
const OffscreenCanvasRenderingContext2D = @This();
|
||||
/// Fill color.
|
||||
/// TODO: Add support for `CanvasGradient` and `CanvasPattern`.
|
||||
_fill_style: color.RGBA = color.RGBA.Named.black,
|
||||
|
||||
pub fn getFillStyle(self: *const OffscreenCanvasRenderingContext2D, page: *Page) ![]const u8 {
|
||||
var w = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
try self._fill_style.format(&w.writer);
|
||||
return w.written();
|
||||
}
|
||||
|
||||
pub fn setFillStyle(
|
||||
self: *OffscreenCanvasRenderingContext2D,
|
||||
value: []const u8,
|
||||
) !void {
|
||||
// Prefer the same fill_style if fails.
|
||||
self._fill_style = color.RGBA.parse(value) catch self._fill_style;
|
||||
}
|
||||
|
||||
pub fn getGlobalAlpha(_: *const OffscreenCanvasRenderingContext2D) f64 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
pub fn getGlobalCompositeOperation(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "source-over";
|
||||
}
|
||||
|
||||
pub fn getStrokeStyle(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "#000000";
|
||||
}
|
||||
|
||||
pub fn getLineWidth(_: *const OffscreenCanvasRenderingContext2D) f64 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
pub fn getLineCap(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "butt";
|
||||
}
|
||||
|
||||
pub fn getLineJoin(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "miter";
|
||||
}
|
||||
|
||||
pub fn getMiterLimit(_: *const OffscreenCanvasRenderingContext2D) f64 {
|
||||
return 10.0;
|
||||
}
|
||||
|
||||
pub fn getFont(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "10px sans-serif";
|
||||
}
|
||||
|
||||
pub fn getTextAlign(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "start";
|
||||
}
|
||||
|
||||
pub fn getTextBaseline(_: *const OffscreenCanvasRenderingContext2D) []const u8 {
|
||||
return "alphabetic";
|
||||
}
|
||||
|
||||
const WidthOrImageData = union(enum) {
|
||||
width: u32,
|
||||
image_data: *ImageData,
|
||||
};
|
||||
|
||||
pub fn createImageData(
|
||||
_: *const OffscreenCanvasRenderingContext2D,
|
||||
width_or_image_data: WidthOrImageData,
|
||||
/// If `ImageData` variant preferred, this is null.
|
||||
maybe_height: ?u32,
|
||||
/// Can be used if width and height provided.
|
||||
maybe_settings: ?ImageData.ConstructorSettings,
|
||||
page: *Page,
|
||||
) !*ImageData {
|
||||
switch (width_or_image_data) {
|
||||
.width => |width| {
|
||||
const height = maybe_height orelse return error.TypeError;
|
||||
return ImageData.constructor(width, height, maybe_settings, page);
|
||||
},
|
||||
.image_data => |image_data| {
|
||||
return ImageData.constructor(image_data._width, image_data._height, null, page);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn putImageData(_: *const OffscreenCanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
||||
|
||||
pub fn save(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn restore(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn scale(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64) void {}
|
||||
pub fn rotate(_: *OffscreenCanvasRenderingContext2D, _: f64) void {}
|
||||
pub fn translate(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64) void {}
|
||||
pub fn transform(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn setTransform(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn resetTransform(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn setGlobalAlpha(_: *OffscreenCanvasRenderingContext2D, _: f64) void {}
|
||||
pub fn setGlobalCompositeOperation(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn setStrokeStyle(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn setLineWidth(_: *OffscreenCanvasRenderingContext2D, _: f64) void {}
|
||||
pub fn setLineCap(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn setLineJoin(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn setMiterLimit(_: *OffscreenCanvasRenderingContext2D, _: f64) void {}
|
||||
pub fn clearRect(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn fillRect(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn strokeRect(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn beginPath(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn closePath(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn moveTo(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64) void {}
|
||||
pub fn lineTo(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64) void {}
|
||||
pub fn quadraticCurveTo(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn bezierCurveTo(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn arc(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64, _: ?bool) void {}
|
||||
pub fn arcTo(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn rect(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64, _: f64, _: f64) void {}
|
||||
pub fn fill(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn stroke(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn clip(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
pub fn setFont(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn setTextAlign(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn setTextBaseline(_: *OffscreenCanvasRenderingContext2D, _: []const u8) void {}
|
||||
pub fn fillText(_: *OffscreenCanvasRenderingContext2D, _: []const u8, _: f64, _: f64, _: ?f64) void {}
|
||||
pub fn strokeText(_: *OffscreenCanvasRenderingContext2D, _: []const u8, _: f64, _: f64, _: ?f64) void {}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(OffscreenCanvasRenderingContext2D);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "OffscreenCanvasRenderingContext2D";
|
||||
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const createImageData = bridge.function(OffscreenCanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
|
||||
pub const putImageData = bridge.function(OffscreenCanvasRenderingContext2D.putImageData, .{});
|
||||
|
||||
pub const save = bridge.function(OffscreenCanvasRenderingContext2D.save, .{});
|
||||
pub const restore = bridge.function(OffscreenCanvasRenderingContext2D.restore, .{});
|
||||
|
||||
pub const scale = bridge.function(OffscreenCanvasRenderingContext2D.scale, .{});
|
||||
pub const rotate = bridge.function(OffscreenCanvasRenderingContext2D.rotate, .{});
|
||||
pub const translate = bridge.function(OffscreenCanvasRenderingContext2D.translate, .{});
|
||||
pub const transform = bridge.function(OffscreenCanvasRenderingContext2D.transform, .{});
|
||||
pub const setTransform = bridge.function(OffscreenCanvasRenderingContext2D.setTransform, .{});
|
||||
pub const resetTransform = bridge.function(OffscreenCanvasRenderingContext2D.resetTransform, .{});
|
||||
|
||||
pub const globalAlpha = bridge.accessor(OffscreenCanvasRenderingContext2D.getGlobalAlpha, OffscreenCanvasRenderingContext2D.setGlobalAlpha, .{});
|
||||
pub const globalCompositeOperation = bridge.accessor(OffscreenCanvasRenderingContext2D.getGlobalCompositeOperation, OffscreenCanvasRenderingContext2D.setGlobalCompositeOperation, .{});
|
||||
|
||||
pub const fillStyle = bridge.accessor(OffscreenCanvasRenderingContext2D.getFillStyle, OffscreenCanvasRenderingContext2D.setFillStyle, .{});
|
||||
pub const strokeStyle = bridge.accessor(OffscreenCanvasRenderingContext2D.getStrokeStyle, OffscreenCanvasRenderingContext2D.setStrokeStyle, .{});
|
||||
|
||||
pub const lineWidth = bridge.accessor(OffscreenCanvasRenderingContext2D.getLineWidth, OffscreenCanvasRenderingContext2D.setLineWidth, .{});
|
||||
pub const lineCap = bridge.accessor(OffscreenCanvasRenderingContext2D.getLineCap, OffscreenCanvasRenderingContext2D.setLineCap, .{});
|
||||
pub const lineJoin = bridge.accessor(OffscreenCanvasRenderingContext2D.getLineJoin, OffscreenCanvasRenderingContext2D.setLineJoin, .{});
|
||||
pub const miterLimit = bridge.accessor(OffscreenCanvasRenderingContext2D.getMiterLimit, OffscreenCanvasRenderingContext2D.setMiterLimit, .{});
|
||||
|
||||
pub const clearRect = bridge.function(OffscreenCanvasRenderingContext2D.clearRect, .{});
|
||||
pub const fillRect = bridge.function(OffscreenCanvasRenderingContext2D.fillRect, .{});
|
||||
pub const strokeRect = bridge.function(OffscreenCanvasRenderingContext2D.strokeRect, .{});
|
||||
|
||||
pub const beginPath = bridge.function(OffscreenCanvasRenderingContext2D.beginPath, .{});
|
||||
pub const closePath = bridge.function(OffscreenCanvasRenderingContext2D.closePath, .{});
|
||||
pub const moveTo = bridge.function(OffscreenCanvasRenderingContext2D.moveTo, .{});
|
||||
pub const lineTo = bridge.function(OffscreenCanvasRenderingContext2D.lineTo, .{});
|
||||
pub const quadraticCurveTo = bridge.function(OffscreenCanvasRenderingContext2D.quadraticCurveTo, .{});
|
||||
pub const bezierCurveTo = bridge.function(OffscreenCanvasRenderingContext2D.bezierCurveTo, .{});
|
||||
pub const arc = bridge.function(OffscreenCanvasRenderingContext2D.arc, .{});
|
||||
pub const arcTo = bridge.function(OffscreenCanvasRenderingContext2D.arcTo, .{});
|
||||
pub const rect = bridge.function(OffscreenCanvasRenderingContext2D.rect, .{});
|
||||
|
||||
pub const fill = bridge.function(OffscreenCanvasRenderingContext2D.fill, .{});
|
||||
pub const stroke = bridge.function(OffscreenCanvasRenderingContext2D.stroke, .{});
|
||||
pub const clip = bridge.function(OffscreenCanvasRenderingContext2D.clip, .{});
|
||||
|
||||
pub const font = bridge.accessor(OffscreenCanvasRenderingContext2D.getFont, OffscreenCanvasRenderingContext2D.setFont, .{});
|
||||
pub const textAlign = bridge.accessor(OffscreenCanvasRenderingContext2D.getTextAlign, OffscreenCanvasRenderingContext2D.setTextAlign, .{});
|
||||
pub const textBaseline = bridge.accessor(OffscreenCanvasRenderingContext2D.getTextBaseline, OffscreenCanvasRenderingContext2D.setTextBaseline, .{});
|
||||
pub const fillText = bridge.function(OffscreenCanvasRenderingContext2D.fillText, .{});
|
||||
pub const strokeText = bridge.function(OffscreenCanvasRenderingContext2D.strokeText, .{});
|
||||
};
|
||||
59
src/browser/webapi/css/FontFaceSet.zig
Normal file
59
src/browser/webapi/css/FontFaceSet.zig
Normal file
@@ -0,0 +1,59 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const FontFaceSet = @This();
|
||||
|
||||
// Padding to avoid zero-size struct, which causes identity_map pointer collisions.
|
||||
_pad: bool = false,
|
||||
|
||||
pub fn init(page: *Page) !*FontFaceSet {
|
||||
return page._factory.create(FontFaceSet{});
|
||||
}
|
||||
|
||||
// FontFaceSet.ready - returns an already-resolved Promise.
|
||||
// In a headless browser there is no font loading, so fonts are always ready.
|
||||
pub fn getReady(_: *FontFaceSet, page: *Page) !js.Promise {
|
||||
return page.js.local.?.resolvePromise({});
|
||||
}
|
||||
|
||||
pub fn getStatus(_: *const FontFaceSet) []const u8 {
|
||||
return "loaded";
|
||||
}
|
||||
|
||||
pub fn getSize(_: *const FontFaceSet) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check(font, text?) - always true; headless has no real fonts to check.
|
||||
pub fn check(_: *const FontFaceSet, font: []const u8) bool {
|
||||
_ = font;
|
||||
return true;
|
||||
}
|
||||
|
||||
// load(font, text?) - resolves immediately with an empty array.
|
||||
pub fn load(_: *FontFaceSet, font: []const u8, page: *Page) !js.Promise {
|
||||
_ = font;
|
||||
return page.js.local.?.resolvePromise({});
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(FontFaceSet);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "FontFaceSet";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const ready = bridge.accessor(FontFaceSet.getReady, null, .{});
|
||||
pub const status = bridge.accessor(FontFaceSet.getStatus, null, .{});
|
||||
pub const size = bridge.accessor(FontFaceSet.getSize, null, .{});
|
||||
pub const check = bridge.function(FontFaceSet.check, .{});
|
||||
pub const load = bridge.function(FontFaceSet.load, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: FontFaceSet" {
|
||||
try testing.htmlRunner("css/font_face_set.html", .{});
|
||||
}
|
||||
@@ -25,6 +25,7 @@ const HtmlElement = @import("../Html.zig");
|
||||
|
||||
const CanvasRenderingContext2D = @import("../../canvas/CanvasRenderingContext2D.zig");
|
||||
const WebGLRenderingContext = @import("../../canvas/WebGLRenderingContext.zig");
|
||||
const OffscreenCanvas = @import("../../canvas/OffscreenCanvas.zig");
|
||||
|
||||
const Canvas = @This();
|
||||
_proto: *HtmlElement,
|
||||
@@ -80,6 +81,14 @@ pub fn getContext(_: *Canvas, context_type: []const u8, page: *Page) !?DrawingCo
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Transfers control of the canvas to an OffscreenCanvas.
|
||||
/// Returns an OffscreenCanvas with the same dimensions.
|
||||
pub fn transferControlToOffscreen(self: *Canvas, page: *Page) !*OffscreenCanvas {
|
||||
const width = self.getWidth();
|
||||
const height = self.getHeight();
|
||||
return OffscreenCanvas.constructor(width, height, page);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Canvas);
|
||||
|
||||
@@ -92,4 +101,5 @@ pub const JsApi = struct {
|
||||
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, .{});
|
||||
pub const transferControlToOffscreen = bridge.function(Canvas.transferControlToOffscreen, .{});
|
||||
};
|
||||
|
||||
@@ -184,6 +184,26 @@ pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getWillValidate(self: *const Input) bool {
|
||||
// An input element is barred from constraint validation if:
|
||||
// - type is hidden, button, or reset
|
||||
// - element is disabled
|
||||
// - element has a datalist ancestor
|
||||
return switch (self._input_type) {
|
||||
.hidden, .button, .reset => false,
|
||||
else => !self.getDisabled() and !self.hasDatalistAncestor(),
|
||||
};
|
||||
}
|
||||
|
||||
fn hasDatalistAncestor(self: *const Input) bool {
|
||||
var node = self.asConstElement().asConstNode().parentElement();
|
||||
while (node) |parent| {
|
||||
if (parent.is(HtmlElement.DataList) != null) return true;
|
||||
node = parent.asConstNode().parentElement();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn getDisabled(self: *const Input) bool {
|
||||
// TODO: Also check for disabled fieldset ancestors
|
||||
// (but not if we're inside a <legend> of that fieldset)
|
||||
@@ -229,7 +249,7 @@ pub fn getMaxLength(self: *const Input) i32 {
|
||||
|
||||
pub fn setMaxLength(self: *Input, max_length: i32, page: *Page) !void {
|
||||
if (max_length < 0) {
|
||||
return error.NegativeValueNotAllowed;
|
||||
return error.IndexSizeError;
|
||||
}
|
||||
var buf: [32]u8 = undefined;
|
||||
const value = std.fmt.bufPrint(&buf, "{d}", .{max_length}) catch unreachable;
|
||||
@@ -868,7 +888,7 @@ pub const JsApi = struct {
|
||||
pub const accept = bridge.accessor(Input.getAccept, Input.setAccept, .{});
|
||||
pub const readOnly = bridge.accessor(Input.getReadonly, Input.setReadonly, .{});
|
||||
pub const alt = bridge.accessor(Input.getAlt, Input.setAlt, .{});
|
||||
pub const maxLength = bridge.accessor(Input.getMaxLength, Input.setMaxLength, .{});
|
||||
pub const maxLength = bridge.accessor(Input.getMaxLength, Input.setMaxLength, .{ .dom_exception = true });
|
||||
pub const size = bridge.accessor(Input.getSize, Input.setSize, .{});
|
||||
pub const src = bridge.accessor(Input.getSrc, Input.setSrc, .{});
|
||||
pub const form = bridge.accessor(Input.getForm, null, .{});
|
||||
@@ -879,6 +899,7 @@ pub const JsApi = struct {
|
||||
pub const step = bridge.accessor(Input.getStep, Input.setStep, .{});
|
||||
pub const multiple = bridge.accessor(Input.getMultiple, Input.setMultiple, .{});
|
||||
pub const autocomplete = bridge.accessor(Input.getAutocomplete, Input.setAutocomplete, .{});
|
||||
pub const willValidate = bridge.accessor(Input.getWillValidate, null, .{});
|
||||
pub const select = bridge.function(Input.select, .{});
|
||||
|
||||
pub const selectionStart = bridge.accessor(Input.getSelectionStart, Input.setSelectionStart, .{});
|
||||
|
||||
@@ -67,6 +67,10 @@ pub fn getText(self: *const Option) []const u8 {
|
||||
return node.getTextContentAlloc(allocator) catch "";
|
||||
}
|
||||
|
||||
pub fn setText(self: *Option, value: []const u8, page: *Page) !void {
|
||||
try self.asNode().setTextContent(value, page);
|
||||
}
|
||||
|
||||
pub fn getSelected(self: *const Option) bool {
|
||||
return self._selected;
|
||||
}
|
||||
@@ -113,7 +117,7 @@ pub const JsApi = struct {
|
||||
};
|
||||
|
||||
pub const value = bridge.accessor(Option.getValue, Option.setValue, .{});
|
||||
pub const text = bridge.accessor(Option.getText, null, .{});
|
||||
pub const text = bridge.accessor(Option.getText, Option.setText, .{});
|
||||
pub const selected = bridge.accessor(Option.getSelected, Option.setSelected, .{});
|
||||
pub const defaultSelected = bridge.accessor(Option.getDefaultSelected, null, .{});
|
||||
pub const disabled = bridge.accessor(Option.getDisabled, Option.setDisabled, .{});
|
||||
|
||||
Reference in New Issue
Block a user