mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1783 from lightpanda-io/custom_element_dynamic_markup_handling
Throw on dynamic markup in custom element callbacks during parsing
This commit is contained in:
@@ -1460,6 +1460,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 {
|
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) {
|
switch (namespace) {
|
||||||
.html => {
|
.html => {
|
||||||
switch (name.len) {
|
switch (name.len) {
|
||||||
@@ -2130,6 +2132,15 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
|||||||
self.js.localScope(&ls);
|
self.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
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;
|
var caught: JS.TryCatch.Caught = undefined;
|
||||||
_ = ls.toLocal(def.constructor).newInstance(&caught) catch |err| {
|
_ = 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 });
|
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 Page = @import("../Page.zig");
|
||||||
const Node = @import("../webapi/Node.zig");
|
const Node = @import("../webapi/Node.zig");
|
||||||
const Element = @import("../webapi/Element.zig");
|
const Element = @import("../webapi/Element.zig");
|
||||||
|
|
||||||
|
pub const AttributeIterator = h5e.AttributeIterator;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
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,
|
_adopted_style_sheets: ?js.Object.Global = null,
|
||||||
_selection: Selection = .init,
|
_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,
|
_on_selectionchange: ?js.Function.Global = null,
|
||||||
|
|
||||||
pub fn getOnSelectionChange(self: *Document) ?js.Function.Global {
|
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;
|
return error.InvalidStateError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self._throw_on_dynamic_markup_insertion_counter > 0) {
|
||||||
|
return error.InvalidStateError;
|
||||||
|
}
|
||||||
|
|
||||||
const html = blk: {
|
const html = blk: {
|
||||||
var joined: std.ArrayList(u8) = .empty;
|
var joined: std.ArrayList(u8) = .empty;
|
||||||
for (text) |str| {
|
for (text) |str| {
|
||||||
@@ -723,6 +732,10 @@ pub fn open(self: *Document, page: *Page) !*Document {
|
|||||||
return error.InvalidStateError;
|
return error.InvalidStateError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self._throw_on_dynamic_markup_insertion_counter > 0) {
|
||||||
|
return error.InvalidStateError;
|
||||||
|
}
|
||||||
|
|
||||||
if (page._load_state == .parsing) {
|
if (page._load_state == .parsing) {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -761,6 +774,10 @@ pub fn close(self: *Document, page: *Page) !void {
|
|||||||
return error.InvalidStateError;
|
return error.InvalidStateError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self._throw_on_dynamic_markup_insertion_counter > 0) {
|
||||||
|
return error.InvalidStateError;
|
||||||
|
}
|
||||||
|
|
||||||
if (self._script_created_parser == null) {
|
if (self._script_created_parser == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user