mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-02 01:15:00 -04:00
Merge pull request #3275 from lightpanda-io/interactive-role-tokens
interactive: read the role attribute as a token list
This commit is contained in:
2 files changed
+46
-11
No files matched your search
@@ -295,7 +295,7 @@ pub fn classifyInteractivity(
|
||||
}
|
||||
|
||||
// 2. ARIA interactive role
|
||||
if (el.getAttributeSafe(comptime .wrap("role"))) |role| {
|
||||
if (explicitRole(el)) |role| {
|
||||
if (isInteractiveRole(role)) return .aria;
|
||||
}
|
||||
|
||||
@@ -367,9 +367,15 @@ pub fn isContentRole(role: []const u8) bool {
|
||||
return content_roles.has(lowered);
|
||||
}
|
||||
|
||||
// ARIA `role` is a space-separated fallback list; the first token wins.
|
||||
pub fn explicitRole(el: *Element) ?[]const u8 {
|
||||
const attr = el.getAttributeSafe(comptime .wrap("role")) orelse return null;
|
||||
var it = std.mem.tokenizeAny(u8, attr, " \t\n\r");
|
||||
return it.next();
|
||||
}
|
||||
|
||||
fn getRole(el: *Element) ?[]const u8 {
|
||||
// Explicit role attribute takes precedence
|
||||
if (el.getAttributeSafe(comptime .wrap("role"))) |role| return role;
|
||||
if (explicitRole(el)) |role| return role;
|
||||
|
||||
// Implicit role from tag
|
||||
return switch (el.getTag()) {
|
||||
@@ -563,6 +569,23 @@ test "browser.interactive: aria role" {
|
||||
try testing.expectEqual(InteractivityType.aria, elements[0].interactivity_type);
|
||||
}
|
||||
|
||||
test "browser.interactive: aria role token list" {
|
||||
const elements = try testInteractive(
|
||||
\\<div role="switch checkbox">Fallback</div>
|
||||
\\<div role=" button ">Padded</div>
|
||||
\\<div role="">Empty</div>
|
||||
\\<button role=" ">Blank</button>
|
||||
);
|
||||
defer testing.test_session.closeAllPages();
|
||||
try testing.expectEqual(3, elements.len);
|
||||
try testing.expectEqual("switch", elements[0].role.?);
|
||||
try testing.expectEqual(InteractivityType.aria, elements[0].interactivity_type);
|
||||
try testing.expectEqual("button", elements[1].role.?);
|
||||
try testing.expectEqual(InteractivityType.aria, elements[1].interactivity_type);
|
||||
try testing.expectEqual("button", elements[2].tag_name);
|
||||
try testing.expectEqual("button", elements[2].role.?);
|
||||
}
|
||||
|
||||
test "browser.interactive: contenteditable" {
|
||||
const elements = try testInteractive("<div contenteditable=\"true\">Edit me</div>");
|
||||
defer testing.test_session.closeAllPages();
|
||||
|
||||
+20
-8
@@ -22,6 +22,7 @@ const lp = @import("lightpanda");
|
||||
const Frame = @import("../browser/Frame.zig");
|
||||
const DOMNode = @import("../browser/webapi/Node.zig");
|
||||
const Label = @import("../browser/webapi/element/html/Label.zig");
|
||||
const interactive = @import("../browser/interactive.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
|
||||
@@ -915,13 +916,7 @@ role_attr: ?[]const u8,
|
||||
pub fn fromNode(dom: *DOMNode) AXNode {
|
||||
return .{
|
||||
.dom = dom,
|
||||
.role_attr = blk: {
|
||||
if (dom.is(DOMNode.Element.Html) == null) {
|
||||
break :blk null;
|
||||
}
|
||||
const elt = dom.as(DOMNode.Element);
|
||||
break :blk elt.getAttributeSafe(comptime .wrap("role"));
|
||||
},
|
||||
.role_attr = if (dom.is(DOMNode.Element.Html) != null) interactive.explicitRole(dom.as(DOMNode.Element)) else null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1429,7 +1424,6 @@ fn isIgnore(self: AXNode, frame: *Frame, cache: *DOMNode.Element.VisibilityCache
|
||||
|
||||
pub fn getRole(self: AXNode) ![]const u8 {
|
||||
if (self.role_attr) |role_value| {
|
||||
// TODO the role can have multiple comma separated values.
|
||||
return role_value;
|
||||
}
|
||||
|
||||
@@ -1790,6 +1784,24 @@ test "AXNode: Writer query filters by role" {
|
||||
try testing.expectEqual("Visible", name_val);
|
||||
}
|
||||
|
||||
test "AXNode: role attribute token list" {
|
||||
var page = try testing.pageTest("cdp/accname.html", .{});
|
||||
defer page.close();
|
||||
const frame = page.frame().?;
|
||||
|
||||
const div = try frame.window._document.createElement("div", null, frame);
|
||||
try Frame.parse.htmlAsChildren(frame, div.asNode(),
|
||||
\\<div role="switch checkbox"></div><div role=" heading "></div><h1 role=""></h1>
|
||||
);
|
||||
|
||||
var child = div.asNode().firstChild().?;
|
||||
try testing.expectEqual("switch", try AXNode.fromNode(child).getRole());
|
||||
child = child.nextSibling().?;
|
||||
try testing.expectEqual("heading", try AXNode.fromNode(child).getRole());
|
||||
child = child.nextSibling().?;
|
||||
try testing.expectEqual("heading", try AXNode.fromNode(child).getRole());
|
||||
}
|
||||
|
||||
test "AXNode: writer maps password input to textbox" {
|
||||
var registry = Node.Registry.init(testing.allocator);
|
||||
defer registry.deinit();
|
||||
|
||||
Reference in new issue
Block a user