mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Merge pull request #721 from lightpanda-io/HTMLInputElement-properties
Input element properties
This commit is contained in:
@@ -22,6 +22,7 @@ const generate = @import("../../runtime/generate.zig");
|
||||
const Env = @import("../env.zig").Env;
|
||||
const Page = @import("../page.zig").Page;
|
||||
|
||||
const urlStitch = @import("../../url.zig").URL.stitch;
|
||||
const URL = @import("../url/url.zig").URL;
|
||||
const Node = @import("../dom/node.zig").Node;
|
||||
const Element = @import("../dom/element.zig").Element;
|
||||
@@ -216,8 +217,7 @@ pub const HTMLAnchorElement = struct {
|
||||
}
|
||||
|
||||
pub fn set_href(self: *parser.Anchor, href: []const u8, page: *const Page) !void {
|
||||
const stitch = @import("../../url.zig").stitch;
|
||||
const full = try stitch(page.call_arena, href, page.url.raw, .{});
|
||||
const full = try urlStitch(page.call_arena, href, page.url.raw, .{});
|
||||
return try parser.anchorSetHref(self, full);
|
||||
}
|
||||
|
||||
@@ -647,6 +647,89 @@ pub const HTMLInputElement = struct {
|
||||
pub const Self = parser.Input;
|
||||
pub const prototype = *HTMLElement;
|
||||
pub const subtype = .node;
|
||||
|
||||
pub fn get_defaultValue(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetDefaultValue(self);
|
||||
}
|
||||
pub fn set_defaultValue(self: *parser.Input, default_value: []const u8) !void {
|
||||
try parser.inputSetDefaultValue(self, default_value);
|
||||
}
|
||||
pub fn get_defaultChecked(self: *parser.Input) !bool {
|
||||
return try parser.inputGetDefaultChecked(self);
|
||||
}
|
||||
pub fn set_defaultChecked(self: *parser.Input, default_checked: bool) !void {
|
||||
try parser.inputSetDefaultChecked(self, default_checked);
|
||||
}
|
||||
pub fn get_form(self: *parser.Input) !?*parser.Form {
|
||||
return try parser.inputGetForm(self);
|
||||
}
|
||||
pub fn get_accept(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetAccept(self);
|
||||
}
|
||||
pub fn set_accept(self: *parser.Input, accept: []const u8) !void {
|
||||
try parser.inputSetAccept(self, accept);
|
||||
}
|
||||
pub fn get_alt(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetAlt(self);
|
||||
}
|
||||
pub fn set_alt(self: *parser.Input, alt: []const u8) !void {
|
||||
try parser.inputSetAlt(self, alt);
|
||||
}
|
||||
pub fn get_checked(self: *parser.Input) !bool {
|
||||
return try parser.inputGetChecked(self);
|
||||
}
|
||||
pub fn set_checked(self: *parser.Input, checked: bool) !void {
|
||||
try parser.inputSetChecked(self, checked);
|
||||
}
|
||||
pub fn get_disabled(self: *parser.Input) !bool {
|
||||
return try parser.inputGetDisabled(self);
|
||||
}
|
||||
pub fn set_disabled(self: *parser.Input, disabled: bool) !void {
|
||||
try parser.inputSetDisabled(self, disabled);
|
||||
}
|
||||
pub fn get_maxLength(self: *parser.Input) !i32 {
|
||||
return try parser.inputGetMaxLength(self);
|
||||
}
|
||||
pub fn set_maxLength(self: *parser.Input, max_length: i32) !void {
|
||||
try parser.inputSetMaxLength(self, max_length);
|
||||
}
|
||||
pub fn get_name(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetName(self);
|
||||
}
|
||||
pub fn set_name(self: *parser.Input, name: []const u8) !void {
|
||||
try parser.inputSetName(self, name);
|
||||
}
|
||||
pub fn get_readOnly(self: *parser.Input) !bool {
|
||||
return try parser.inputGetReadOnly(self);
|
||||
}
|
||||
pub fn set_readOnly(self: *parser.Input, read_only: bool) !void {
|
||||
try parser.inputSetReadOnly(self, read_only);
|
||||
}
|
||||
pub fn get_size(self: *parser.Input) !u32 {
|
||||
return try parser.inputGetSize(self);
|
||||
}
|
||||
pub fn set_size(self: *parser.Input, size: i32) !void {
|
||||
try parser.inputSetSize(self, size);
|
||||
}
|
||||
pub fn get_src(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetSrc(self);
|
||||
}
|
||||
pub fn set_src(self: *parser.Input, src: []const u8, page: *Page) !void {
|
||||
const new_src = try urlStitch(page.call_arena, src, page.url.raw, .{ .alloc = .if_needed });
|
||||
try parser.inputSetSrc(self, new_src);
|
||||
}
|
||||
pub fn get_type(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetType(self);
|
||||
}
|
||||
pub fn set_type(self: *parser.Input, type_: []const u8) !void {
|
||||
try parser.inputSetType(self, type_);
|
||||
}
|
||||
pub fn get_value(self: *parser.Input) ![]const u8 {
|
||||
return try parser.inputGetValue(self);
|
||||
}
|
||||
pub fn set_value(self: *parser.Input, value: []const u8) !void {
|
||||
try parser.inputSetValue(self, value);
|
||||
}
|
||||
};
|
||||
|
||||
pub const HTMLLIElement = struct {
|
||||
@@ -1210,3 +1293,139 @@ test "Browser.HTML.Element" {
|
||||
.{ "document.activeElement === focused", "true" },
|
||||
}, .{});
|
||||
}
|
||||
test "Browser.HTML.HtmlInputElement.propeties" {
|
||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" });
|
||||
defer runner.deinit();
|
||||
var alloc = std.heap.ArenaAllocator.init(runner.app.allocator);
|
||||
defer alloc.deinit();
|
||||
const arena = alloc.allocator();
|
||||
|
||||
try runner.testCases(&.{.{ "let elem_input = document.createElement('input')", null }}, .{});
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.form", "null" }}, .{}); // Initial value
|
||||
// Valid input.form is tested separately :Browser.HTML.HtmlInputElement.propeties.form
|
||||
try testProperty(arena, &runner, "elem_input.form", "null", &.{.{ .input = "'foo'" }}); // Invalid
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.accept", "" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.accept", null, &str_valids); // Valid
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.alt", "" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.alt", null, &str_valids); // Valid
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.disabled", "false" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.disabled", null, &bool_valids); // Valid
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.maxLength", "-1" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.maxLength", null, &.{.{ .input = "5" }}); // Valid
|
||||
try testProperty(arena, &runner, "elem_input.maxLength", "0", &.{.{ .input = "'banana'" }}); // Invalid
|
||||
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.maxLength = -45", null }}, .{})); // Error
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.name", "" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.name", null, &str_valids); // Valid
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.readOnly", "false" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.readOnly", null, &bool_valids); // Valid
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.size", "20" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.size", null, &.{.{ .input = "5" }}); // Valid
|
||||
try testProperty(arena, &runner, "elem_input.size", "20", &.{.{ .input = "-26" }}); // Invalid
|
||||
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 0", null }}, .{})); // Error
|
||||
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 'banana'", null }}, .{})); // Error
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.src", "" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.src", null, &.{
|
||||
.{ .input = "'foo'", .expected = "https://lightpanda.io/foo" }, // TODO stitch should work with spaces -> %20
|
||||
.{ .input = "-3", .expected = "https://lightpanda.io/-3" },
|
||||
.{ .input = "''", .expected = "https://lightpanda.io/noslashattheend" },
|
||||
});
|
||||
|
||||
try runner.testCases(&.{.{ "elem_input.type", "text" }}, .{}); // Initial value
|
||||
try testProperty(arena, &runner, "elem_input.type", null, &.{.{ .input = "'checkbox'", .expected = "checkbox" }}); // Valid
|
||||
try testProperty(arena, &runner, "elem_input.type", "text", &.{.{ .input = "'5'" }}); // Invalid
|
||||
|
||||
// Properties that are related
|
||||
try runner.testCases(&.{
|
||||
.{ "let input_checked = document.createElement('input')", null },
|
||||
.{ "input_checked.defaultChecked", "false" },
|
||||
.{ "input_checked.checked", "false" },
|
||||
|
||||
.{ "input_checked.defaultChecked = true", "true" },
|
||||
.{ "input_checked.defaultChecked", "true" },
|
||||
.{ "input_checked.checked", "true" }, // Also perceived as true
|
||||
|
||||
.{ "input_checked.checked = false", "false" },
|
||||
.{ "input_checked.defaultChecked", "true" },
|
||||
.{ "input_checked.checked", "false" },
|
||||
|
||||
.{ "input_checked.defaultChecked = true", "true" },
|
||||
.{ "input_checked.checked", "false" }, // Still false
|
||||
}, .{});
|
||||
try runner.testCases(&.{
|
||||
.{ "let input_value = document.createElement('input')", null },
|
||||
.{ "input_value.defaultValue", "" },
|
||||
.{ "input_value.value", "" },
|
||||
|
||||
.{ "input_value.defaultValue = 3.1", "3.1" },
|
||||
.{ "input_value.defaultValue", "3.1" },
|
||||
.{ "input_value.value", "3.1" }, // Also perceived as 3.1
|
||||
|
||||
.{ "input_value.value = 'mango'", "mango" },
|
||||
.{ "input_value.defaultValue", "3.1" },
|
||||
.{ "input_value.value", "mango" },
|
||||
|
||||
.{ "input_value.defaultValue = true", "true" },
|
||||
.{ "input_value.value", "mango" }, // Still mango
|
||||
}, .{});
|
||||
}
|
||||
test "Browser.HTML.HtmlInputElement.propeties.form" {
|
||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
|
||||
\\ <form action="test.php" target="_blank">
|
||||
\\ <p>
|
||||
\\ <label>First name: <input type="text" name="first-name" /></label>
|
||||
\\ </p>
|
||||
\\ </form>
|
||||
});
|
||||
defer runner.deinit();
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let elem_input = document.querySelector('input')", null },
|
||||
}, .{});
|
||||
try runner.testCases(&.{.{ "elem_input.form", "[object HTMLFormElement]" }}, .{}); // Initial value
|
||||
try runner.testCases(&.{
|
||||
.{ "elem_input.form = 'foo'", null },
|
||||
.{ "elem_input.form", "[object HTMLFormElement]" }, // Invalid
|
||||
}, .{});
|
||||
}
|
||||
|
||||
const Check = struct {
|
||||
input: []const u8,
|
||||
expected: ?[]const u8 = null, // Needed when input != expected
|
||||
};
|
||||
const bool_valids = [_]Check{
|
||||
.{ .input = "true" },
|
||||
.{ .input = "''", .expected = "false" },
|
||||
.{ .input = "13.5", .expected = "true" },
|
||||
};
|
||||
const str_valids = [_]Check{
|
||||
.{ .input = "'foo'", .expected = "foo" },
|
||||
.{ .input = "5", .expected = "5" },
|
||||
.{ .input = "''", .expected = "" },
|
||||
.{ .input = "document", .expected = "[object HTMLDocument]" },
|
||||
};
|
||||
|
||||
// .{ "elem.type = '5'", "5" },
|
||||
// .{ "elem.type", "text" },
|
||||
fn testProperty(
|
||||
arena: std.mem.Allocator,
|
||||
runner: *testing.JsRunner,
|
||||
elem_dot_prop: []const u8,
|
||||
always: ?[]const u8, // Ignores checks' expected if set
|
||||
checks: []const Check,
|
||||
) !void {
|
||||
for (checks) |check| {
|
||||
try runner.testCases(&.{
|
||||
.{ try std.mem.concat(arena, u8, &.{ elem_dot_prop, " = ", check.input }), null },
|
||||
.{ elem_dot_prop, always orelse check.expected orelse check.input },
|
||||
}, .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2498,14 +2498,6 @@ pub fn optionSetSelected(option: *Option, selected: bool) !void {
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
// Input
|
||||
pub fn inputGetChecked(input: *Input) !bool {
|
||||
var b: bool = false;
|
||||
const err = c.dom_html_input_element_get_checked(input, &b);
|
||||
try DOMErr(err);
|
||||
return b;
|
||||
}
|
||||
|
||||
// HtmlCollection
|
||||
pub fn htmlCollectionGetLength(collection: *HTMLCollection) !u32 {
|
||||
var len: u32 = 0;
|
||||
@@ -2601,3 +2593,183 @@ pub fn imageSetIsMap(image: *Image, is_map: bool) !void {
|
||||
const err = c.dom_html_image_element_set_is_map(image, is_map);
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
// Input
|
||||
// - Input.align is deprecated
|
||||
// - Input.useMap is deprecated
|
||||
// - HTMLElement.access_key
|
||||
// - HTMLElement.tabIndex
|
||||
// TODO methods:
|
||||
// - HTMLElement.blur
|
||||
// - HTMLElement.focus
|
||||
// - select
|
||||
// - HTMLElement.click
|
||||
|
||||
pub fn inputGetDefaultValue(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_default_value(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "";
|
||||
return strToData(s);
|
||||
}
|
||||
pub fn inputSetDefaultValue(input: *Input, default_value: []const u8) !void {
|
||||
const err = c.dom_html_input_element_set_default_value(input, try strFromData(default_value));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetDefaultChecked(input: *Input) !bool {
|
||||
var default_checked: bool = false;
|
||||
const err = c.dom_html_input_element_get_default_checked(input, &default_checked);
|
||||
try DOMErr(err);
|
||||
return default_checked;
|
||||
}
|
||||
pub fn inputSetDefaultChecked(input: *Input, default_checked: bool) !void {
|
||||
const err = c.dom_html_input_element_set_default_checked(input, default_checked);
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetForm(input: *Input) !?*Form {
|
||||
var form: ?*Form = null;
|
||||
const err = c.dom_html_input_element_get_form(input, &form);
|
||||
try DOMErr(err);
|
||||
return form;
|
||||
}
|
||||
|
||||
pub fn inputGetAccept(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_accept(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "";
|
||||
return strToData(s);
|
||||
}
|
||||
pub fn inputSetAccept(input: *Input, accept: []const u8) !void {
|
||||
const err = c.dom_html_input_element_set_accept(input, try strFromData(accept));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetAlt(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_alt(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "";
|
||||
return strToData(s);
|
||||
}
|
||||
pub fn inputSetAlt(input: *Input, alt: []const u8) !void {
|
||||
const err = c.dom_html_input_element_set_alt(input, try strFromData(alt));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetChecked(input: *Input) !bool {
|
||||
var checked: bool = false;
|
||||
const err = c.dom_html_input_element_get_checked(input, &checked);
|
||||
try DOMErr(err);
|
||||
return checked;
|
||||
}
|
||||
pub fn inputSetChecked(input: *Input, checked: bool) !void {
|
||||
const err = c.dom_html_input_element_set_checked(input, checked);
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetDisabled(input: *Input) !bool {
|
||||
var disabled: bool = false;
|
||||
const err = c.dom_html_input_element_get_disabled(input, &disabled);
|
||||
try DOMErr(err);
|
||||
return disabled;
|
||||
}
|
||||
pub fn inputSetDisabled(input: *Input, disabled: bool) !void {
|
||||
const err = c.dom_html_input_element_set_disabled(input, disabled);
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetMaxLength(input: *Input) !i32 {
|
||||
var max_length: i32 = 0;
|
||||
const err = c.dom_html_input_element_get_max_length(input, &max_length);
|
||||
try DOMErr(err);
|
||||
return max_length;
|
||||
}
|
||||
pub fn inputSetMaxLength(input: *Input, max_length: i32) !void {
|
||||
if (max_length < 0) return error.NegativeValueNotAllowed;
|
||||
const err = c.dom_html_input_element_set_max_length(input, @intCast(max_length));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetName(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_name(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "";
|
||||
return strToData(s);
|
||||
}
|
||||
pub fn inputSetName(input: *Input, name: []const u8) !void {
|
||||
const err = c.dom_html_input_element_set_name(input, try strFromData(name));
|
||||
try DOMErr(err);
|
||||
}
|
||||
pub fn inputGetReadOnly(input: *Input) !bool {
|
||||
var read_only: bool = false;
|
||||
const err = c.dom_html_input_element_get_read_only(input, &read_only);
|
||||
try DOMErr(err);
|
||||
return read_only;
|
||||
}
|
||||
pub fn inputSetReadOnly(input: *Input, read_only: bool) !void {
|
||||
const err = c.dom_html_input_element_set_read_only(input, read_only);
|
||||
try DOMErr(err);
|
||||
}
|
||||
pub fn inputGetSize(input: *Input) !u32 {
|
||||
var size: u32 = 0;
|
||||
const err = c.dom_html_input_element_get_size(input, &size);
|
||||
try DOMErr(err);
|
||||
if (size == ulongNegativeOne) return 20; // 20
|
||||
return size;
|
||||
}
|
||||
pub fn inputSetSize(input: *Input, size: i32) !void {
|
||||
if (size == 0) return error.ZeroNotAllowed;
|
||||
const new_size = if (size < 0) 20 else size;
|
||||
const err = c.dom_html_input_element_set_size(input, @intCast(new_size));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetSrc(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_src(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "";
|
||||
return strToData(s);
|
||||
}
|
||||
// url should already be stitched!
|
||||
pub fn inputSetSrc(input: *Input, src: []const u8) !void {
|
||||
const err = c.dom_html_input_element_set_src(input, try strFromData(src));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
pub fn inputGetType(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_type(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "text";
|
||||
return strToData(s);
|
||||
}
|
||||
pub fn inputSetType(input: *Input, type_: []const u8) !void {
|
||||
// @speed sort values by usage frequency/length
|
||||
const possible_values = [_][]const u8{ "text", "search", "tel", "url", "email", "password", "date", "month", "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", "file", "hidden", "image", "button", "submit", "reset" };
|
||||
var found = false;
|
||||
for (possible_values) |item| {
|
||||
if (std.mem.eql(u8, type_, item)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const new_type = if (found) type_ else "text";
|
||||
try elementSetAttribute(@ptrCast(input), "type", new_type);
|
||||
}
|
||||
|
||||
pub fn inputGetValue(input: *Input) ![]const u8 {
|
||||
var s_: ?*String = null;
|
||||
const err = c.dom_html_input_element_get_value(input, &s_);
|
||||
try DOMErr(err);
|
||||
const s = s_ orelse return "";
|
||||
return strToData(s);
|
||||
}
|
||||
pub fn inputSetValue(input: *Input, value: []const u8) !void {
|
||||
const err = c.dom_html_input_element_set_value(input, try strFromData(value));
|
||||
try DOMErr(err);
|
||||
}
|
||||
|
||||
16
src/url.zig
16
src/url.zig
@@ -110,6 +110,12 @@ pub const URL = struct {
|
||||
}
|
||||
return src;
|
||||
}
|
||||
if (src.len == 0) {
|
||||
if (opts.alloc == .always) {
|
||||
return allocator.dupe(u8, base);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
const protocol_end: usize = blk: {
|
||||
if (std.mem.indexOf(u8, base, "://")) |protocol_index| {
|
||||
@@ -256,6 +262,16 @@ test "URL: Stiching src as full path" {
|
||||
try testing.expectString("https://lightpanda.io/something.js", result);
|
||||
}
|
||||
|
||||
test "URL: Stitching Base & Src URLs (empty src)" {
|
||||
const allocator = testing.allocator;
|
||||
|
||||
const base = "https://www.google.com/xyz/abc/123";
|
||||
const src = "";
|
||||
const result = try URL.stitch(allocator, src, base, .{});
|
||||
defer allocator.free(result);
|
||||
try testing.expectString("https://www.google.com/xyz/abc/123", result);
|
||||
}
|
||||
|
||||
test "URL: concatQueryString" {
|
||||
defer testing.reset();
|
||||
const arena = testing.arena_allocator;
|
||||
|
||||
2
vendor/netsurf/libdom
vendored
2
vendor/netsurf/libdom
vendored
Submodule vendor/netsurf/libdom updated: f8fc21702b...9a1d1e41d1
Reference in New Issue
Block a user