diff --git a/src/browser/tests/element/html/input-attrs.html b/src/browser/tests/element/html/input-attrs.html index 3e3bf606..41f0e75a 100644 --- a/src/browser/tests/element/html/input-attrs.html +++ b/src/browser/tests/element/html/input-attrs.html @@ -85,3 +85,77 @@ testing.expectEqual('', i3.autocomplete); } + + diff --git a/src/browser/webapi/element/html/Input.zig b/src/browser/webapi/element/html/Input.zig index be82f254..d6175e8c 100644 --- a/src/browser/webapi/element/html/Input.zig +++ b/src/browser/webapi/element/html/Input.zig @@ -564,7 +564,7 @@ fn sanitizeValue(self: *Input, comptime dupe: bool, value: []const u8, frame: *F .time => return if (isValidTime(value)) if (comptime dupe) try frame.dupeString(value) else value else "", .@"datetime-local" => return try sanitizeDatetimeLocal(dupe, value, frame.arena), .number => return if (isValidFloatingPoint(value)) if (comptime dupe) try frame.dupeString(value) else value else "", - .range => return if (isValidFloatingPoint(value)) if (comptime dupe) try frame.dupeString(value) else value else "50", + .range => return try sanitizeRange(dupe, value, self.getMin(), self.getMax(), frame), .color => { if (value.len == 7 and value[0] == '#') { var needs_lower = false; @@ -786,6 +786,46 @@ fn sanitizeDatetimeLocal(comptime dupe: bool, value: []const u8, arena: std.mem. return result[0..total_len]; } +/// Sanitize value for `` per WHATWG HTML spec: +/// https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range) +/// 1. If value is not a valid floating-point number, set it to +/// `min + (max - min) / 2`. +/// 2. If value < min, set it to min. +/// 3. If value > max, set it to max. +/// `min`/`max` default to 0 and 100 respectively when the attribute is missing +/// or fails to parse as a valid floating-point number. Step matching is not +/// applied here. +fn sanitizeRange( + comptime dupe: bool, + value: []const u8, + min_attr: []const u8, + max_attr: []const u8, + frame: *Frame, +) ![]const u8 { + const min: f64 = if (isValidFloatingPoint(min_attr)) + std.fmt.parseFloat(f64, min_attr) catch 0 + else + 0; + const max: f64 = if (isValidFloatingPoint(max_attr)) + std.fmt.parseFloat(f64, max_attr) catch 100 + else + 100; + + if (!isValidFloatingPoint(value)) { + return try formatFloat(frame.arena, min + (max - min) / 2); + } + + const v = std.fmt.parseFloat(f64, value) catch unreachable; // grammar already validated + if (v < min) return try formatFloat(frame.arena, min); + if (v > max) return try formatFloat(frame.arena, max); + return if (comptime dupe) try frame.dupeString(value) else value; +} + +/// Format an f64 to its shortest decimal representation, arena-allocated. +fn formatFloat(arena: std.mem.Allocator, value: f64) ![]const u8 { + return std.fmt.allocPrint(arena, "{d}", .{value}); +} + /// Parse a slice that must be ALL ASCII digits into a u32. Returns null if any non-digit or empty. fn parseAllDigits(s: []const u8) ?u32 { if (s.len == 0) return null;