From 74ec80b76be9643f1b2b3ce398f3fad2be1ec0ef Mon Sep 17 00:00:00 2001 From: Jeremy Wood Date: Mon, 27 Apr 2026 19:48:57 +0000 Subject: [PATCH] 8379953: [macos] VoiceOver Reads "Header" instead of "Heading" Reviewed-by: kizune, prr --- .../awt/a11y/CommonComponentAccessibility.m | 4 + .../8379953/VoiceOverHeaderRole.java | 83 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 test/jdk/javax/accessibility/8379953/VoiceOverHeaderRole.java diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m index 40f9f50a7d7..edc36f15015 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m @@ -849,6 +849,10 @@ static jobject sAccessibilityClass = NULL; parent != nil && ![[parent javaRole] isEqualToString:@"combobox"] ) { fNSRole = NSAccessibilityMenuRole; + } else if ( [javaRole isEqualToString:@"header"]) { + if (@available(macOS 26, *)) { + fNSRole = NSAccessibilityHeadingRole; + } } if (fNSRole == nil) { // this component has assigned itself a custom AccessibleRole not in the sRoles array diff --git a/test/jdk/javax/accessibility/8379953/VoiceOverHeaderRole.java b/test/jdk/javax/accessibility/8379953/VoiceOverHeaderRole.java new file mode 100644 index 00000000000..cb90902473d --- /dev/null +++ b/test/jdk/javax/accessibility/8379953/VoiceOverHeaderRole.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/* + * @test + * @key headful + * @bug 8379953 + * @summary manual test for VoiceOver reading header/heading correctly + * @requires os.family == "mac" + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual VoiceOverHeaderRole + */ + +public class VoiceOverHeaderRole { + public static void main(String[] args) throws Exception { + String INSTRUCTIONS = "INSTRUCTIONS (Mac-only):\n" + + "1. Open VoiceOver\n" + + "2. Move the VoiceOver cursor over the JLabel.\n\n" + + "Expected behavior: VoiceOver should identify it as a " + + "\"heading\". It should not say \"header\".\n\n" + + "If you select the link using \"Accessibility " + + "Inspector\": it should identify its role as AXHeading."; + + PassFailJFrame.builder() + .title("VoiceOverHeaderRole Instruction") + .instructions(INSTRUCTIONS) + .columns(40) + .testUI(VoiceOverHeaderRole::createUI) + .build() + .awaitAndCheck(); + } + + private static JFrame createUI() { + JPanel p = new JPanel(); + JLabel label = new JLabel("Octopus") { + @Override + public AccessibleContext getAccessibleContext() { + if (accessibleContext == null) { + accessibleContext = new AccessibleJLabel() { + @Override + public AccessibleRole getAccessibleRole() { + return AccessibleRole.HEADER; + } + }; + } + return accessibleContext; + } + }; + p.add(label); + + JFrame frame = new JFrame(); + frame.getContentPane().add(p); + frame.pack(); + return frame; + } +}