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)
+
+