mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
SemanticTree: improve accessibility tree and name calculation
- Add more structural roles (banner, navigation, main, list, etc.). - Implement fallback for accessible names (SVG titles, image alt text). - Skip children for leaf-like semantic nodes to reduce redundancy. - Disable pruning in the default semantic tree view.
This commit is contained in:
@@ -385,9 +385,17 @@ const JsonVisitor = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn isStructuralRole(role: []const u8) bool {
|
fn isStructuralRole(role: []const u8) bool {
|
||||||
|
// zig fmt: off
|
||||||
return std.mem.eql(u8, role, "none") or
|
return std.mem.eql(u8, role, "none") or
|
||||||
std.mem.eql(u8, role, "generic") or
|
std.mem.eql(u8, role, "generic") or
|
||||||
std.mem.eql(u8, role, "InlineTextBox");
|
std.mem.eql(u8, role, "InlineTextBox") or
|
||||||
|
std.mem.eql(u8, role, "banner") or
|
||||||
|
std.mem.eql(u8, role, "navigation") or
|
||||||
|
std.mem.eql(u8, role, "main") or
|
||||||
|
std.mem.eql(u8, role, "list") or
|
||||||
|
std.mem.eql(u8, role, "listitem") or
|
||||||
|
std.mem.eql(u8, role, "region");
|
||||||
|
// zig fmt: on
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextVisitor = struct {
|
const TextVisitor = struct {
|
||||||
@@ -436,6 +444,17 @@ const TextVisitor = struct {
|
|||||||
|
|
||||||
try self.writer.writeByte('\n');
|
try self.writer.writeByte('\n');
|
||||||
self.depth += 1;
|
self.depth += 1;
|
||||||
|
|
||||||
|
// If this is a leaf-like semantic node and we already have a name,
|
||||||
|
// skip children to avoid redundant StaticText or noise.
|
||||||
|
const is_leaf_semantic = std.mem.eql(u8, data.role, "link") or
|
||||||
|
std.mem.eql(u8, data.role, "button") or
|
||||||
|
std.mem.eql(u8, data.role, "heading") or
|
||||||
|
std.mem.eql(u8, data.role, "code");
|
||||||
|
if (is_leaf_semantic and data.name != null and data.name.?.len > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -888,10 +888,12 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource {
|
|||||||
=> {},
|
=> {},
|
||||||
else => {
|
else => {
|
||||||
// write text content if exists.
|
// write text content if exists.
|
||||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
var buf: std.Io.Writer.Allocating = .init(page.call_arena);
|
||||||
try el.getInnerText(&buf.writer);
|
try writeAccessibleNameFallback(node, &buf.writer, page);
|
||||||
try writeString(buf.written(), w);
|
if (buf.written().len > 0) {
|
||||||
return .contents;
|
try writeString(buf.written(), w);
|
||||||
|
return .contents;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,6 +917,40 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn writeAccessibleNameFallback(node: *DOMNode, writer: *std.Io.Writer, page: *Page) !void {
|
||||||
|
var it = node.childrenIterator();
|
||||||
|
while (it.next()) |child| {
|
||||||
|
switch (child._type) {
|
||||||
|
.cdata => |cd| switch (cd._type) {
|
||||||
|
.text => |*text| try writer.writeAll(text.getWholeText()),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
.element => |el| {
|
||||||
|
if (el.getTag() == .img) {
|
||||||
|
if (el.getAttributeSafe(.wrap("alt"))) |alt| {
|
||||||
|
try writer.writeAll(alt);
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
}
|
||||||
|
} else if (el.getTag() == .svg) {
|
||||||
|
// Try to find a <title> inside SVG
|
||||||
|
var sit = child.childrenIterator();
|
||||||
|
while (sit.next()) |s_child| {
|
||||||
|
if (s_child.is(DOMNode.Element)) |s_el| {
|
||||||
|
if (std.mem.eql(u8, s_el.getTagNameLower(), "title")) {
|
||||||
|
try writeAccessibleNameFallback(s_child, writer, page);
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try writeAccessibleNameFallback(child, writer, page);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn isHidden(elt: *DOMNode.Element) bool {
|
fn isHidden(elt: *DOMNode.Element) bool {
|
||||||
if (elt.getAttributeSafe(comptime .wrap("aria-hidden"))) |value| {
|
if (elt.getAttributeSafe(comptime .wrap("aria-hidden"))) |value| {
|
||||||
if (std.mem.eql(u8, value, "true")) {
|
if (std.mem.eql(u8, value, "true")) {
|
||||||
|
|||||||
@@ -113,12 +113,12 @@ pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void {
|
|||||||
var registry = CDPNode.Registry.init(app.allocator);
|
var registry = CDPNode.Registry.init(app.allocator);
|
||||||
defer registry.deinit();
|
defer registry.deinit();
|
||||||
|
|
||||||
const st = SemanticTree{
|
const st: SemanticTree = .{
|
||||||
.dom_node = page.window._document.asNode(),
|
.dom_node = page.window._document.asNode(),
|
||||||
.registry = ®istry,
|
.registry = ®istry,
|
||||||
.page = page,
|
.page = page,
|
||||||
.arena = page.call_arena,
|
.arena = page.call_arena,
|
||||||
.prune = true,
|
.prune = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mode == .semantic_tree) {
|
if (mode == .semantic_tree) {
|
||||||
|
|||||||
Reference in New Issue
Block a user