mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1603 from egrs/wpt-spec-guards
spec compliance: missing validation guards
This commit is contained in:
@@ -681,9 +681,10 @@ const ActivationState = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Commit: fire input and change events only if state actually changed
|
// Commit: fire input and change events only if state actually changed
|
||||||
|
// and the element is connected to a document (detached elements don't fire).
|
||||||
// For checkboxes, state always changes. For radios, only if was unchecked.
|
// For checkboxes, state always changes. For radios, only if was unchecked.
|
||||||
const state_changed = (input._input_type == .checkbox) or !self.old_checked;
|
const state_changed = (input._input_type == .checkbox) or !self.old_checked;
|
||||||
if (state_changed) {
|
if (state_changed and input.asElement().asNode().isConnected()) {
|
||||||
fireEvent(page, input, "input") catch |err| {
|
fireEvent(page, input, "input") catch |err| {
|
||||||
log.warn(.event, "input event", .{ .err = err });
|
log.warn(.event, "input event", .{ .err = err });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any
|
|||||||
const js_err: *const v8.Value = switch (err) {
|
const js_err: *const v8.Value = switch (err) {
|
||||||
error.TryCatchRethrow => return,
|
error.TryCatchRethrow => return,
|
||||||
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
||||||
|
error.TypeError => isolate.createTypeError(""),
|
||||||
error.OutOfMemory => isolate.createError("out of memory"),
|
error.OutOfMemory => isolate.createError("out of memory"),
|
||||||
error.IllegalConstructor => isolate.createError("Illegal Contructor"),
|
error.IllegalConstructor => isolate.createError("Illegal Contructor"),
|
||||||
else => blk: {
|
else => blk: {
|
||||||
|
|||||||
@@ -256,9 +256,9 @@ pub const JsApi = struct {
|
|||||||
pub const childElementCount = bridge.accessor(DocumentFragment.getChildElementCount, null, .{});
|
pub const childElementCount = bridge.accessor(DocumentFragment.getChildElementCount, null, .{});
|
||||||
pub const firstElementChild = bridge.accessor(DocumentFragment.firstElementChild, null, .{});
|
pub const firstElementChild = bridge.accessor(DocumentFragment.firstElementChild, null, .{});
|
||||||
pub const lastElementChild = bridge.accessor(DocumentFragment.lastElementChild, null, .{});
|
pub const lastElementChild = bridge.accessor(DocumentFragment.lastElementChild, null, .{});
|
||||||
pub const append = bridge.function(DocumentFragment.append, .{});
|
pub const append = bridge.function(DocumentFragment.append, .{ .dom_exception = true });
|
||||||
pub const prepend = bridge.function(DocumentFragment.prepend, .{});
|
pub const prepend = bridge.function(DocumentFragment.prepend, .{ .dom_exception = true });
|
||||||
pub const replaceChildren = bridge.function(DocumentFragment.replaceChildren, .{});
|
pub const replaceChildren = bridge.function(DocumentFragment.replaceChildren, .{ .dom_exception = true });
|
||||||
pub const innerHTML = bridge.accessor(_innerHTML, DocumentFragment.setInnerHTML, .{});
|
pub const innerHTML = bridge.accessor(_innerHTML, DocumentFragment.setInnerHTML, .{});
|
||||||
|
|
||||||
fn _innerHTML(self: *DocumentFragment, page: *Page) ![]const u8 {
|
fn _innerHTML(self: *DocumentFragment, page: *Page) ![]const u8 {
|
||||||
|
|||||||
@@ -1612,13 +1612,13 @@ pub const JsApi = struct {
|
|||||||
fn _attachShadow(self: *Element, init: ShadowRootInit, page: *Page) !*ShadowRoot {
|
fn _attachShadow(self: *Element, init: ShadowRootInit, page: *Page) !*ShadowRoot {
|
||||||
return self.attachShadow(init.mode, page);
|
return self.attachShadow(init.mode, page);
|
||||||
}
|
}
|
||||||
pub const replaceChildren = bridge.function(Element.replaceChildren, .{});
|
pub const replaceChildren = bridge.function(Element.replaceChildren, .{ .dom_exception = true });
|
||||||
pub const replaceWith = bridge.function(Element.replaceWith, .{});
|
pub const replaceWith = bridge.function(Element.replaceWith, .{ .dom_exception = true });
|
||||||
pub const remove = bridge.function(Element.remove, .{});
|
pub const remove = bridge.function(Element.remove, .{});
|
||||||
pub const append = bridge.function(Element.append, .{});
|
pub const append = bridge.function(Element.append, .{ .dom_exception = true });
|
||||||
pub const prepend = bridge.function(Element.prepend, .{});
|
pub const prepend = bridge.function(Element.prepend, .{ .dom_exception = true });
|
||||||
pub const before = bridge.function(Element.before, .{});
|
pub const before = bridge.function(Element.before, .{ .dom_exception = true });
|
||||||
pub const after = bridge.function(Element.after, .{});
|
pub const after = bridge.function(Element.after, .{ .dom_exception = true });
|
||||||
pub const firstElementChild = bridge.accessor(Element.firstElementChild, null, .{});
|
pub const firstElementChild = bridge.accessor(Element.firstElementChild, null, .{});
|
||||||
pub const lastElementChild = bridge.accessor(Element.lastElementChild, null, .{});
|
pub const lastElementChild = bridge.accessor(Element.lastElementChild, null, .{});
|
||||||
pub const nextElementSibling = bridge.accessor(Element.nextElementSibling, null, .{});
|
pub const nextElementSibling = bridge.accessor(Element.nextElementSibling, null, .{});
|
||||||
|
|||||||
@@ -189,8 +189,10 @@ pub fn getCurrentTarget(self: *const Event) ?*EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn preventDefault(self: *Event) void {
|
pub fn preventDefault(self: *Event) void {
|
||||||
|
if (self._cancelable) {
|
||||||
self._prevent_default = true;
|
self._prevent_default = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stopPropagation(self: *Event) void {
|
pub fn stopPropagation(self: *Event) void {
|
||||||
self._stop_propagation = true;
|
self._stop_propagation = true;
|
||||||
@@ -210,7 +212,12 @@ pub fn getReturnValue(self: *const Event) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setReturnValue(self: *Event, v: bool) void {
|
pub fn setReturnValue(self: *Event, v: bool) void {
|
||||||
self._prevent_default = !v;
|
if (!v) {
|
||||||
|
// Setting returnValue=false is equivalent to preventDefault()
|
||||||
|
if (self._cancelable) {
|
||||||
|
self._prevent_default = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCancelBubble(self: *const Event) bool {
|
pub fn getCancelBubble(self: *const Event) bool {
|
||||||
|
|||||||
@@ -49,10 +49,11 @@ node: std.DoublyLinkedList.Node = .{},
|
|||||||
|
|
||||||
const Observing = struct {
|
const Observing = struct {
|
||||||
target: *Node,
|
target: *Node,
|
||||||
options: ObserveOptions,
|
options: ResolvedOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ObserveOptions = struct {
|
/// Internal options with all nullable bools resolved to concrete values.
|
||||||
|
const ResolvedOptions = struct {
|
||||||
attributes: bool = false,
|
attributes: bool = false,
|
||||||
attributeOldValue: bool = false,
|
attributeOldValue: bool = false,
|
||||||
childList: bool = false,
|
childList: bool = false,
|
||||||
@@ -62,6 +63,16 @@ pub const ObserveOptions = struct {
|
|||||||
attributeFilter: ?[]const []const u8 = null,
|
attributeFilter: ?[]const []const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ObserveOptions = struct {
|
||||||
|
attributes: ?bool = null,
|
||||||
|
attributeOldValue: ?bool = null,
|
||||||
|
childList: bool = false,
|
||||||
|
characterData: ?bool = null,
|
||||||
|
characterDataOldValue: ?bool = null,
|
||||||
|
subtree: bool = false,
|
||||||
|
attributeFilter: ?[]const []const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
|
pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
|
||||||
const arena = try page.getArena(.{ .debug = "MutationObserver" });
|
const arena = try page.getArena(.{ .debug = "MutationObserver" });
|
||||||
errdefer page.releaseArena(arena);
|
errdefer page.releaseArena(arena);
|
||||||
@@ -88,24 +99,61 @@ pub fn deinit(self: *MutationObserver, shutdown: bool) void {
|
|||||||
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
|
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
|
||||||
const arena = self._arena;
|
const arena = self._arena;
|
||||||
|
|
||||||
|
// Per spec: if attributeOldValue/attributeFilter present and attributes
|
||||||
|
// not explicitly set, imply attributes=true. Same for characterData.
|
||||||
|
var resolved = options;
|
||||||
|
if (resolved.attributes == null and (resolved.attributeOldValue != null or resolved.attributeFilter != null)) {
|
||||||
|
resolved.attributes = true;
|
||||||
|
}
|
||||||
|
if (resolved.characterData == null and resolved.characterDataOldValue != null) {
|
||||||
|
resolved.characterData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = resolved.attributes orelse false;
|
||||||
|
const character_data = resolved.characterData orelse false;
|
||||||
|
|
||||||
|
// Validate: at least one of childList/attributes/characterData must be true
|
||||||
|
if (!resolved.childList and !attributes and !character_data) {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate: attributeOldValue/attributeFilter require attributes != false
|
||||||
|
if ((resolved.attributeOldValue orelse false) and !attributes) {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
if (resolved.attributeFilter != null and !attributes) {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate: characterDataOldValue requires characterData != false
|
||||||
|
if ((resolved.characterDataOldValue orelse false) and !character_data) {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build resolved options with concrete bool values
|
||||||
|
var store_options = ResolvedOptions{
|
||||||
|
.attributes = attributes,
|
||||||
|
.attributeOldValue = resolved.attributeOldValue orelse false,
|
||||||
|
.childList = resolved.childList,
|
||||||
|
.characterData = character_data,
|
||||||
|
.characterDataOldValue = resolved.characterDataOldValue orelse false,
|
||||||
|
.subtree = resolved.subtree,
|
||||||
|
.attributeFilter = resolved.attributeFilter,
|
||||||
|
};
|
||||||
|
|
||||||
// Deep copy attributeFilter if present
|
// Deep copy attributeFilter if present
|
||||||
var copied_options = options;
|
|
||||||
if (options.attributeFilter) |filter| {
|
if (options.attributeFilter) |filter| {
|
||||||
const filter_copy = try arena.alloc([]const u8, filter.len);
|
const filter_copy = try arena.alloc([]const u8, filter.len);
|
||||||
for (filter, 0..) |name, i| {
|
for (filter, 0..) |name, i| {
|
||||||
filter_copy[i] = try arena.dupe(u8, name);
|
filter_copy[i] = try arena.dupe(u8, name);
|
||||||
}
|
}
|
||||||
copied_options.attributeFilter = filter_copy;
|
store_options.attributeFilter = filter_copy;
|
||||||
}
|
|
||||||
|
|
||||||
if (options.characterDataOldValue) {
|
|
||||||
copied_options.characterData = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already observing this target
|
// Check if already observing this target
|
||||||
for (self._observing.items) |*obs| {
|
for (self._observing.items) |*obs| {
|
||||||
if (obs.target == target) {
|
if (obs.target == target) {
|
||||||
obs.options = copied_options;
|
obs.options = store_options;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +166,7 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
|
|||||||
|
|
||||||
try self._observing.append(arena, .{
|
try self._observing.append(arena, .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.options = copied_options,
|
.options = store_options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ fn validateNodeInsertion(parent: *Node, node: *Node) !void {
|
|||||||
if (node._type == .attribute) {
|
if (node._type == .attribute) {
|
||||||
return error.HierarchyError;
|
return error.HierarchyError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Doctype nodes can only be inserted into a Document
|
||||||
|
if (node._type == .document_type and parent._type != .document) {
|
||||||
|
return error.HierarchyError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
||||||
|
|||||||
Reference in New Issue
Block a user