mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
browser: improve visibility and interactivity CSS checks
Adds support for `pointer-events: none` in interactivity classification and expands `checkVisibility` to include `visibility` and `opacity`. Refactors CSS property lookup into a shared helper.
This commit is contained in:
@@ -133,7 +133,7 @@ fn walk(self: @This(), node: *Node, xpath_buffer: *std.ArrayList(u8), parent_nam
|
||||
}
|
||||
|
||||
if (el.is(Element.Html)) |html_el| {
|
||||
if (interactive.classifyInteractivity(el, html_el, listener_targets) != null) {
|
||||
if (interactive.classifyInteractivity(self.page, el, html_el, listener_targets) != null) {
|
||||
is_interactive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ pub fn collectInteractiveElements(
|
||||
else => {},
|
||||
}
|
||||
|
||||
const itype = classifyInteractivity(el, html_el, listener_targets) orelse continue;
|
||||
const itype = classifyInteractivity(page, el, html_el, listener_targets) orelse continue;
|
||||
|
||||
const listener_types = getListenerTypes(
|
||||
el.asEventTarget(),
|
||||
@@ -210,10 +210,13 @@ pub fn buildListenerTargetMap(page: *Page, arena: Allocator) !ListenerTargetMap
|
||||
}
|
||||
|
||||
pub fn classifyInteractivity(
|
||||
page: *Page,
|
||||
el: *Element,
|
||||
html_el: *Element.Html,
|
||||
listener_targets: ListenerTargetMap,
|
||||
) ?InteractivityType {
|
||||
if (el.hasPointerEventsNone(page)) return null;
|
||||
|
||||
// 1. Native interactive by tag
|
||||
switch (el.getTag()) {
|
||||
.button, .summary, .details, .select, .textarea => return .native,
|
||||
@@ -519,6 +522,11 @@ test "browser.interactive: disabled by fieldset" {
|
||||
try testing.expect(!elements[1].disabled);
|
||||
}
|
||||
|
||||
test "browser.interactive: pointer-events none" {
|
||||
const elements = try testInteractive("<button style=\"pointer-events: none;\">Click me</button>");
|
||||
try testing.expectEqual(0, elements.len);
|
||||
}
|
||||
|
||||
test "browser.interactive: non-interactive div" {
|
||||
const elements = try testInteractive("<div>Just text</div>");
|
||||
try testing.expectEqual(0, elements.len);
|
||||
|
||||
@@ -77,3 +77,33 @@
|
||||
testing.expectEqual('\uFFFDabc', CSS.escape('\x00abc'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="checkVisibility">
|
||||
{
|
||||
const el = document.createElement("div");
|
||||
document.documentElement.appendChild(el);
|
||||
testing.expectEqual(true, el.checkVisibility());
|
||||
|
||||
el.style.display = "none";
|
||||
testing.expectEqual(false, el.checkVisibility());
|
||||
|
||||
el.style.display = "block";
|
||||
testing.expectEqual(true, el.checkVisibility());
|
||||
|
||||
el.style.visibility = "hidden";
|
||||
testing.expectEqual(false, el.checkVisibility());
|
||||
|
||||
el.style.visibility = "collapse";
|
||||
testing.expectEqual(false, el.checkVisibility());
|
||||
|
||||
el.style.visibility = "visible";
|
||||
testing.expectEqual(true, el.checkVisibility());
|
||||
|
||||
el.style.opacity = "0";
|
||||
testing.expectEqual(false, el.checkVisibility());
|
||||
|
||||
el.style.opacity = "1";
|
||||
testing.expectEqual(true, el.checkVisibility());
|
||||
document.documentElement.removeChild(el);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1042,20 +1042,26 @@ pub fn parentElement(self: *Element) ?*Element {
|
||||
}
|
||||
|
||||
const CSSStyleRule = @import("css/CSSStyleRule.zig");
|
||||
const StyleSheetList = @import("css/StyleSheetList.zig");
|
||||
|
||||
pub fn checkVisibility(self: *Element, page: *Page) bool {
|
||||
pub fn hasPointerEventsNone(self: *Element, page: *Page) bool {
|
||||
const doc_sheets = page.document.getStyleSheets(page) catch null;
|
||||
var current: ?*Element = self;
|
||||
|
||||
while (current) |el| {
|
||||
if (el.getStyle(page)) |style| {
|
||||
const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
|
||||
if (std.mem.eql(u8, display, "none")) {
|
||||
if (checkCssProperty(el, page, doc_sheets, "pointer-events", &[_][]const u8{"none"})) return true;
|
||||
current = el.parentElement();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn checkCssProperty(el: *Element, page: *Page, doc_sheets: ?*StyleSheetList, property_name: []const u8, target_values: []const []const u8) bool {
|
||||
if (el.getOrCreateStyle(page) catch null) |style| {
|
||||
const val = style.asCSSStyleDeclaration().getPropertyValue(property_name, page);
|
||||
for (target_values) |target| {
|
||||
if (std.mem.eql(u8, val, target)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if any global stylesheet hides this element
|
||||
if (doc_sheets) |sheets| {
|
||||
for (0..sheets.length()) |i| {
|
||||
const sheet = sheets.item(i) orelse continue;
|
||||
@@ -1064,18 +1070,28 @@ pub fn checkVisibility(self: *Element, page: *Page) bool {
|
||||
const rule = rules.item(j) orelse continue;
|
||||
if (rule.is(CSSStyleRule)) |style_rule| {
|
||||
const selector = style_rule.getSelectorText();
|
||||
const does_match = el.matches(selector, page) catch false;
|
||||
if (does_match) {
|
||||
if (el.matches(selector, page) catch false) {
|
||||
const style = (style_rule.getStyle(page) catch continue).asCSSStyleDeclaration();
|
||||
const display = style.getPropertyValue("display", page);
|
||||
if (std.mem.eql(u8, display, "none")) {
|
||||
const val = style.getPropertyValue(property_name, page);
|
||||
for (target_values) |target| {
|
||||
if (std.mem.eql(u8, val, target)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkVisibility(self: *Element, page: *Page) bool {
|
||||
const doc_sheets = page.document.getStyleSheets(page) catch null;
|
||||
var current: ?*Element = self;
|
||||
|
||||
while (current) |el| {
|
||||
if (checkCssProperty(el, page, doc_sheets, "display", &[_][]const u8{"none"})) return false;
|
||||
if (checkCssProperty(el, page, doc_sheets, "visibility", &[_][]const u8{ "hidden", "collapse" })) return false;
|
||||
if (checkCssProperty(el, page, doc_sheets, "opacity", &[_][]const u8{"0"})) return false;
|
||||
|
||||
current = el.parentElement();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user