diff --git a/src/browser/Page.zig b/src/browser/Page.zig index bf87fad7..5e4578c1 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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; diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 3a991313..46a7e235 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -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"), diff --git a/src/browser/tests/canvas/offscreen_canvas.html b/src/browser/tests/canvas/offscreen_canvas.html new file mode 100644 index 00000000..f162213a --- /dev/null +++ b/src/browser/tests/canvas/offscreen_canvas.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + diff --git a/src/browser/tests/css/font_face_set.html b/src/browser/tests/css/font_face_set.html new file mode 100644 index 00000000..a860669e --- /dev/null +++ b/src/browser/tests/css/font_face_set.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/element/html/input.html b/src/browser/tests/element/html/input.html index 8fab2b5a..2f8940cc 100644 --- a/src/browser/tests/element/html/input.html +++ b/src/browser/tests/element/html/input.html @@ -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; diff --git a/src/browser/tests/element/html/option.html b/src/browser/tests/element/html/option.html index 6e7f72c8..45983370 100644 --- a/src/browser/tests/element/html/option.html +++ b/src/browser/tests/element/html/option.html @@ -29,6 +29,12 @@ testing.expectEqual('Text 3', $('#opt3').text) + +