mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Throw on dynamic markup in custom element callbacks during parsing
Custom element callbacks aren't allowed to call document.open/close/write while parsing. Fixes WPT crash: /custom-elements/throw-on-dynamic-markup-insertion-counter-reactions.html
This commit is contained in:
@@ -1459,6 +1459,8 @@ pub fn adoptNodeTree(self: *Page, node: *Node, new_owner: *Document) !void {
|
||||
}
|
||||
|
||||
pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const u8, attribute_iterator: anytype) !*Node {
|
||||
const from_parser = @TypeOf(attribute_iterator) == Parser.AttributeIterator;
|
||||
|
||||
switch (namespace) {
|
||||
.html => {
|
||||
switch (name.len) {
|
||||
@@ -2129,6 +2131,15 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
||||
self.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
if (from_parser) {
|
||||
// There are some things custom elements aren't allowed to do
|
||||
// when we're parsing.
|
||||
self.document._throw_on_dynamic_markup_insertion_counter += 1;
|
||||
}
|
||||
defer if (from_parser) {
|
||||
self.document._throw_on_dynamic_markup_insertion_counter -= 1;
|
||||
};
|
||||
|
||||
var caught: JS.TryCatch.Caught = undefined;
|
||||
_ = ls.toLocal(def.constructor).newInstance(&caught) catch |err| {
|
||||
log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught, .type = self._type, .url = self.url });
|
||||
|
||||
@@ -23,6 +23,9 @@ const h5e = @import("html5ever.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Node = @import("../webapi/Node.zig");
|
||||
const Element = @import("../webapi/Element.zig");
|
||||
|
||||
pub const AttributeIterator = h5e.AttributeIterator;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<script src="../testing.js"></script>
|
||||
<script>
|
||||
// Test that document.open/write/close throw InvalidStateError during custom element
|
||||
// reactions when the element is parsed from HTML
|
||||
|
||||
window.constructorOpenException = null;
|
||||
window.constructorWriteException = null;
|
||||
window.constructorCloseException = null;
|
||||
window.constructorCalled = false;
|
||||
|
||||
class ThrowTestElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
window.constructorCalled = true;
|
||||
|
||||
// Try document.open on the same document during constructor - should throw
|
||||
try {
|
||||
document.open();
|
||||
} catch (e) {
|
||||
window.constructorOpenException = e;
|
||||
}
|
||||
|
||||
// Try document.write on the same document during constructor - should throw
|
||||
try {
|
||||
document.write('<b>test</b>');
|
||||
} catch (e) {
|
||||
window.constructorWriteException = e;
|
||||
}
|
||||
|
||||
// Try document.close on the same document during constructor - should throw
|
||||
try {
|
||||
document.close();
|
||||
} catch (e) {
|
||||
window.constructorCloseException = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('throw-test-element', ThrowTestElement);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- This element will be parsed from HTML, triggering the constructor -->
|
||||
<throw-test-element id="test-element"></throw-test-element>
|
||||
|
||||
<script id="verify_throws">
|
||||
{
|
||||
// Verify the constructor was called
|
||||
testing.expectEqual(true, window.constructorCalled);
|
||||
|
||||
// Verify document.open threw InvalidStateError
|
||||
testing.expectEqual(true, window.constructorOpenException !== null);
|
||||
testing.expectEqual('InvalidStateError', window.constructorOpenException.name);
|
||||
|
||||
// Verify document.write threw InvalidStateError
|
||||
testing.expectEqual(true, window.constructorWriteException !== null);
|
||||
testing.expectEqual('InvalidStateError', window.constructorWriteException.name);
|
||||
|
||||
// Verify document.close threw InvalidStateError
|
||||
testing.expectEqual(true, window.constructorCloseException !== null);
|
||||
testing.expectEqual('InvalidStateError', window.constructorCloseException.name);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
@@ -63,6 +63,11 @@ _script_created_parser: ?Parser.Streaming = null,
|
||||
_adopted_style_sheets: ?js.Object.Global = null,
|
||||
_selection: Selection = .init,
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter
|
||||
// Incremented during custom element reactions when parsing. When > 0,
|
||||
// document.open/close/write/writeln must throw InvalidStateError.
|
||||
_throw_on_dynamic_markup_insertion_counter: u32 = 0,
|
||||
|
||||
_on_selectionchange: ?js.Function.Global = null,
|
||||
|
||||
pub fn getOnSelectionChange(self: *Document) ?js.Function.Global {
|
||||
@@ -641,6 +646,10 @@ pub fn write(self: *Document, text: []const []const u8, page: *Page) !void {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (self._throw_on_dynamic_markup_insertion_counter > 0) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
const html = blk: {
|
||||
var joined: std.ArrayList(u8) = .empty;
|
||||
for (text) |str| {
|
||||
@@ -723,6 +732,10 @@ pub fn open(self: *Document, page: *Page) !*Document {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (self._throw_on_dynamic_markup_insertion_counter > 0) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (page._load_state == .parsing) {
|
||||
return self;
|
||||
}
|
||||
@@ -761,6 +774,10 @@ pub fn close(self: *Document, page: *Page) !void {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (self._throw_on_dynamic_markup_insertion_counter > 0) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (self._script_created_parser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user