diff --git a/src/SemanticTree.zig b/src/SemanticTree.zig
index 1a0910d2..b60b3ea6 100644
--- a/src/SemanticTree.zig
+++ b/src/SemanticTree.zig
@@ -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;
}
}
diff --git a/src/browser/interactive.zig b/src/browser/interactive.zig
index 2d03db51..fa45257f 100644
--- a/src/browser/interactive.zig
+++ b/src/browser/interactive.zig
@@ -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("");
+ try testing.expectEqual(0, elements.len);
+}
+
test "browser.interactive: non-interactive div" {
const elements = try testInteractive("
Just text
");
try testing.expectEqual(0, elements.len);
diff --git a/src/browser/tests/css.html b/src/browser/tests/css.html
index 70fdff47..ede62285 100644
--- a/src/browser/tests/css.html
+++ b/src/browser/tests/css.html
@@ -77,3 +77,33 @@
testing.expectEqual('\uFFFDabc', CSS.escape('\x00abc'));
}
+
+
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index 0d41ee1f..9fa62280 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -1042,40 +1042,56 @@ pub fn parentElement(self: *Element) ?*Element {
}
const CSSStyleRule = @import("css/CSSStyleRule.zig");
+const StyleSheetList = @import("css/StyleSheetList.zig");
+
+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 (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;
+ }
+ }
+
+ if (doc_sheets) |sheets| {
+ for (0..sheets.length()) |i| {
+ const sheet = sheets.item(i) orelse continue;
+ const rules = sheet.getCssRules(page) catch continue;
+ for (0..rules.length()) |j| {
+ const rule = rules.item(j) orelse continue;
+ if (rule.is(CSSStyleRule)) |style_rule| {
+ const selector = style_rule.getSelectorText();
+ if (el.matches(selector, page) catch false) {
+ const style = (style_rule.getStyle(page) catch continue).asCSSStyleDeclaration();
+ 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 (el.getStyle(page)) |style| {
- const display = style.asCSSStyleDeclaration().getPropertyValue("display", page);
- if (std.mem.eql(u8, display, "none")) {
- return false;
- }
- }
-
- // 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;
- const rules = sheet.getCssRules(page) catch continue;
- for (0..rules.length()) |j| {
- 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) {
- const style = (style_rule.getStyle(page) catch continue).asCSSStyleDeclaration();
- const display = style.getPropertyValue("display", page);
- if (std.mem.eql(u8, display, "none")) {
- return false;
- }
- }
- }
- }
- }
- }
+ 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();
}