diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 889e0d3c..8a9b9ffb 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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 }); diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index 08adfb47..f259bdd5 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -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; diff --git a/src/browser/tests/custom_elements/throw_on_dynamic_markup_insertion.html b/src/browser/tests/custom_elements/throw_on_dynamic_markup_insertion.html new file mode 100644 index 00000000..06739297 --- /dev/null +++ b/src/browser/tests/custom_elements/throw_on_dynamic_markup_insertion.html @@ -0,0 +1,66 @@ + + + + + + + + + + + diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 74a260e8..53a0b07a 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -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; }