mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
fix input value defaults, color normalization, and event propagation resets
- checkbox/radio getValue() returns "on" when no value attribute set - color input sanitization normalizes hex to lowercase per spec - initial input value is sanitized per input type during element creation - initEvent resets both stop_propagation and stop_immediate_propagation - dispatchNode resets propagation flags after dispatch per DOM spec step 12
This commit is contained in:
@@ -262,6 +262,8 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
// and default actions execute (unless prevented)
|
||||
defer {
|
||||
event._event_phase = .none;
|
||||
event._stop_propagation = false;
|
||||
event._stop_immediate_propagation = false;
|
||||
// Handle checkbox/radio activation rollback or commit
|
||||
if (activation_state) |state| {
|
||||
state.restore(event, page);
|
||||
@@ -322,19 +324,18 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
var i: usize = path_len;
|
||||
while (i > 1) {
|
||||
i -= 1;
|
||||
if (event._stop_propagation) return;
|
||||
const current_target = path[i];
|
||||
if (self.lookup.get(.{
|
||||
.event_target = @intFromPtr(current_target),
|
||||
.type_string = event._type_string,
|
||||
})) |list| {
|
||||
try self.dispatchPhase(list, current_target, event, was_handled, true);
|
||||
if (event._stop_propagation) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: At target
|
||||
if (event._stop_propagation) return;
|
||||
event._event_phase = .at_target;
|
||||
const target_et = target.asEventTarget();
|
||||
|
||||
@@ -375,14 +376,12 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
||||
if (event._bubbles) {
|
||||
event._event_phase = .bubbling_phase;
|
||||
for (path[1..]) |current_target| {
|
||||
if (event._stop_propagation) break;
|
||||
if (self.lookup.get(.{
|
||||
.type_string = event._type_string,
|
||||
.event_target = @intFromPtr(current_target),
|
||||
})) |list| {
|
||||
try self.dispatchPhase(list, current_target, event, was_handled, false);
|
||||
if (event._stop_propagation) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,8 @@ pub fn initEvent(
|
||||
self._bubbles = bubbles orelse false;
|
||||
self._cancelable = cancelable orelse false;
|
||||
self._stop_propagation = false;
|
||||
self._stop_immediate_propagation = false;
|
||||
self._prevent_default = false;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Event, shutdown: bool) void {
|
||||
|
||||
@@ -125,7 +125,10 @@ pub fn setType(self: *Input, typ: []const u8, page: *Page) !void {
|
||||
}
|
||||
|
||||
pub fn getValue(self: *const Input) []const u8 {
|
||||
return self._value orelse self._default_value orelse "";
|
||||
return self._value orelse self._default_value orelse switch (self._input_type) {
|
||||
.checkbox, .radio => "on",
|
||||
else => "",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setValue(self: *Input, value: []const u8, page: *Page) !void {
|
||||
@@ -474,10 +477,19 @@ fn sanitizeValue(self: *Input, value: []const u8, page: *Page) ![]const u8 {
|
||||
},
|
||||
.color => {
|
||||
if (value.len == 7 and value[0] == '#') {
|
||||
var needs_lower = false;
|
||||
for (value[1..]) |c| {
|
||||
if (!std.ascii.isHex(c)) return "#000000";
|
||||
if (c >= 'A' and c <= 'F') needs_lower = true;
|
||||
}
|
||||
return value;
|
||||
if (!needs_lower) return value;
|
||||
// Normalize to lowercase per spec
|
||||
const result = try page.call_arena.alloc(u8, 7);
|
||||
result[0] = '#';
|
||||
for (value[1..], 0..) |c, j| {
|
||||
result[j + 1] = std.ascii.toLower(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return "#000000";
|
||||
},
|
||||
@@ -581,8 +593,6 @@ pub const Build = struct {
|
||||
self._default_value = element.getAttributeSafe(comptime .wrap("value"));
|
||||
self._default_checked = element.getAttributeSafe(comptime .wrap("checked")) != null;
|
||||
|
||||
// Current state starts equal to default
|
||||
self._value = self._default_value;
|
||||
self._checked = self._default_checked;
|
||||
|
||||
self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr|
|
||||
@@ -590,6 +600,20 @@ pub const Build = struct {
|
||||
else
|
||||
.text;
|
||||
|
||||
// Current value starts equal to default, but sanitized per input type.
|
||||
// sanitizeValue allocates temporaries from call_arena, so we must
|
||||
// persist any new buffer into page.arena for the value to survive.
|
||||
if (self._default_value) |dv| {
|
||||
const sanitized = try self.sanitizeValue(dv, page);
|
||||
if (sanitized.ptr == dv.ptr and sanitized.len == dv.len) {
|
||||
self._value = self._default_value;
|
||||
} else {
|
||||
self._value = try page.arena.dupe(u8, sanitized);
|
||||
}
|
||||
} else {
|
||||
self._value = null;
|
||||
}
|
||||
|
||||
// If this is a checked radio button, uncheck others in its group
|
||||
if (self._checked and self._input_type == .radio) {
|
||||
try self.uncheckRadioGroup(page);
|
||||
|
||||
Reference in New Issue
Block a user