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)
|
// and default actions execute (unless prevented)
|
||||||
defer {
|
defer {
|
||||||
event._event_phase = .none;
|
event._event_phase = .none;
|
||||||
|
event._stop_propagation = false;
|
||||||
|
event._stop_immediate_propagation = false;
|
||||||
// Handle checkbox/radio activation rollback or commit
|
// Handle checkbox/radio activation rollback or commit
|
||||||
if (activation_state) |state| {
|
if (activation_state) |state| {
|
||||||
state.restore(event, page);
|
state.restore(event, page);
|
||||||
@@ -322,19 +324,18 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
var i: usize = path_len;
|
var i: usize = path_len;
|
||||||
while (i > 1) {
|
while (i > 1) {
|
||||||
i -= 1;
|
i -= 1;
|
||||||
|
if (event._stop_propagation) return;
|
||||||
const current_target = path[i];
|
const current_target = path[i];
|
||||||
if (self.lookup.get(.{
|
if (self.lookup.get(.{
|
||||||
.event_target = @intFromPtr(current_target),
|
.event_target = @intFromPtr(current_target),
|
||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
})) |list| {
|
})) |list| {
|
||||||
try self.dispatchPhase(list, current_target, event, was_handled, true);
|
try self.dispatchPhase(list, current_target, event, was_handled, true);
|
||||||
if (event._stop_propagation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: At target
|
// Phase 2: At target
|
||||||
|
if (event._stop_propagation) return;
|
||||||
event._event_phase = .at_target;
|
event._event_phase = .at_target;
|
||||||
const target_et = target.asEventTarget();
|
const target_et = target.asEventTarget();
|
||||||
|
|
||||||
@@ -375,14 +376,12 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
if (event._bubbles) {
|
if (event._bubbles) {
|
||||||
event._event_phase = .bubbling_phase;
|
event._event_phase = .bubbling_phase;
|
||||||
for (path[1..]) |current_target| {
|
for (path[1..]) |current_target| {
|
||||||
|
if (event._stop_propagation) break;
|
||||||
if (self.lookup.get(.{
|
if (self.lookup.get(.{
|
||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
.event_target = @intFromPtr(current_target),
|
.event_target = @intFromPtr(current_target),
|
||||||
})) |list| {
|
})) |list| {
|
||||||
try self.dispatchPhase(list, current_target, event, was_handled, false);
|
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._bubbles = bubbles orelse false;
|
||||||
self._cancelable = cancelable orelse false;
|
self._cancelable = cancelable orelse false;
|
||||||
self._stop_propagation = false;
|
self._stop_propagation = false;
|
||||||
|
self._stop_immediate_propagation = false;
|
||||||
|
self._prevent_default = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Event, shutdown: bool) void {
|
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 {
|
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 {
|
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 => {
|
.color => {
|
||||||
if (value.len == 7 and value[0] == '#') {
|
if (value.len == 7 and value[0] == '#') {
|
||||||
|
var needs_lower = false;
|
||||||
for (value[1..]) |c| {
|
for (value[1..]) |c| {
|
||||||
if (!std.ascii.isHex(c)) return "#000000";
|
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";
|
return "#000000";
|
||||||
},
|
},
|
||||||
@@ -581,8 +593,6 @@ pub const Build = struct {
|
|||||||
self._default_value = element.getAttributeSafe(comptime .wrap("value"));
|
self._default_value = element.getAttributeSafe(comptime .wrap("value"));
|
||||||
self._default_checked = element.getAttributeSafe(comptime .wrap("checked")) != null;
|
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._checked = self._default_checked;
|
||||||
|
|
||||||
self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr|
|
self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr|
|
||||||
@@ -590,6 +600,20 @@ pub const Build = struct {
|
|||||||
else
|
else
|
||||||
.text;
|
.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 this is a checked radio button, uncheck others in its group
|
||||||
if (self._checked and self._input_type == .radio) {
|
if (self._checked and self._input_type == .radio) {
|
||||||
try self.uncheckRadioGroup(page);
|
try self.uncheckRadioGroup(page);
|
||||||
|
|||||||
Reference in New Issue
Block a user