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 (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;
|
is_interactive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ pub fn collectInteractiveElements(
|
|||||||
else => {},
|
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(
|
const listener_types = getListenerTypes(
|
||||||
el.asEventTarget(),
|
el.asEventTarget(),
|
||||||
@@ -210,10 +210,13 @@ pub fn buildListenerTargetMap(page: *Page, arena: Allocator) !ListenerTargetMap
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn classifyInteractivity(
|
pub fn classifyInteractivity(
|
||||||
|
page: *Page,
|
||||||
el: *Element,
|
el: *Element,
|
||||||
html_el: *Element.Html,
|
html_el: *Element.Html,
|
||||||
listener_targets: ListenerTargetMap,
|
listener_targets: ListenerTargetMap,
|
||||||
) ?InteractivityType {
|
) ?InteractivityType {
|
||||||
|
if (el.hasPointerEventsNone(page)) return null;
|
||||||
|
|
||||||
// 1. Native interactive by tag
|
// 1. Native interactive by tag
|
||||||
switch (el.getTag()) {
|
switch (el.getTag()) {
|
||||||
.button, .summary, .details, .select, .textarea => return .native,
|
.button, .summary, .details, .select, .textarea => return .native,
|
||||||
@@ -519,6 +522,11 @@ test "browser.interactive: disabled by fieldset" {
|
|||||||
try testing.expect(!elements[1].disabled);
|
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" {
|
test "browser.interactive: non-interactive div" {
|
||||||
const elements = try testInteractive("<div>Just text</div>");
|
const elements = try testInteractive("<div>Just text</div>");
|
||||||
try testing.expectEqual(0, elements.len);
|
try testing.expectEqual(0, elements.len);
|
||||||
|
|||||||
@@ -77,3 +77,33 @@
|
|||||||
testing.expectEqual('\uFFFDabc', CSS.escape('\x00abc'));
|
testing.expectEqual('\uFFFDabc', CSS.escape('\x00abc'));
|
||||||
}
|
}
|
||||||
</script>
|
</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 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;
|
const doc_sheets = page.document.getStyleSheets(page) catch null;
|
||||||
var current: ?*Element = self;
|
var current: ?*Element = self;
|
||||||
|
|
||||||
while (current) |el| {
|
while (current) |el| {
|
||||||
if (el.getStyle(page)) |style| {
|
if (checkCssProperty(el, page, doc_sheets, "pointer-events", &[_][]const u8{"none"})) return true;
|
||||||
const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
|
current = el.parentElement();
|
||||||
if (std.mem.eql(u8, display, "none")) {
|
}
|
||||||
return false;
|
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| {
|
if (doc_sheets) |sheets| {
|
||||||
for (0..sheets.length()) |i| {
|
for (0..sheets.length()) |i| {
|
||||||
const sheet = sheets.item(i) orelse continue;
|
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;
|
const rule = rules.item(j) orelse continue;
|
||||||
if (rule.is(CSSStyleRule)) |style_rule| {
|
if (rule.is(CSSStyleRule)) |style_rule| {
|
||||||
const selector = style_rule.getSelectorText();
|
const selector = style_rule.getSelectorText();
|
||||||
const does_match = el.matches(selector, page) catch false;
|
if (el.matches(selector, page) catch false) {
|
||||||
if (does_match) {
|
|
||||||
const style = (style_rule.getStyle(page) catch continue).asCSSStyleDeclaration();
|
const style = (style_rule.getStyle(page) catch continue).asCSSStyleDeclaration();
|
||||||
const display = style.getPropertyValue("display", page);
|
const val = style.getPropertyValue(property_name, page);
|
||||||
if (std.mem.eql(u8, display, "none")) {
|
for (target_values) |target| {
|
||||||
|
if (std.mem.eql(u8, val, target)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
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();
|
current = el.parentElement();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user