Community
Participate
Working Groups
3.4 M5 on Mac OS X Leopard Carbon Adding a Browser (HIWebViewCreate) to a Shell on Leopard seems to mess up accessibility. Typing Tab moves focus from one Carbon control to the next, and the focus highlight follows the focus, but the VoiceOver cursor does not follow the focus - in fact, VoiceOver can't even tell that the focused control exists. Consequently, VoiceOver does not speak any of the other controls. For example, this breaks accessibility in the SWT ControlExample because of the Browser tab... even when the Browser tab is not visible. Looking at this with the Accessibility Inspector, it seems that the Browser has essentially "hijacked" the Shell as far as accessibility is concerned, because other controls in the Shell don't show up in the accessible hierarchy. Looking at it with UI Browser, I can see the other controls in the hierarchy, however there is a [MISMATCH] that seems to indicate a disconnect in the hierarchy below the level of the Shell. The following PI code (platform calls from within Java) shows the problem, however I do not know how to fix this. I need help from Scott at Apple. 1) Run the code below from inside eclipse with SWT in your workspace. 2) Turn on VoiceOver (command F5) and type the Tab key a bunch of times. Notice that the VoiceOver cursor (black rectangle) follows focus, and VoiceOver speaks the names of the 2 Carbon buttons. 3) Now, in the PI code, set createBrowser = true; and run again. Type tab. This time, VoiceOver doesn't speak the names of the Carbon buttons. 4) Run the Accessibility Inspector (in /Developer/Applications/Utilities/Accessibility Tools) and note that the inspector cannot see either of the 2 buttons. 5) If you have UI Browser, then you need to create a Mac application and run that - ask me, and I will send steps to create an application. You can see the broken hierarchy in UI Browser. import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.carbon.*; import org.eclipse.swt.internal.cocoa.*; public class CarbonPI { boolean createCocoaButtons = false; boolean createBrowser = false; int shellHandle, rootHandle; boolean running = true; int queue; int carbonButton1, carbonButton2; int cocoaButton1, cocoaButton2; int webViewHandle; public static void main(String[] args) { new CarbonPI().doit(); } void doit() { int [] psn = new int [2]; if (OS.GetCurrentProcess (psn) == OS.noErr) { String APP_NAME = "Test Application"; byte[] buffer = new byte[APP_NAME.length () + 1]; for (int i = 0; i < buffer.length - 1; i++) { buffer[i] = (byte) APP_NAME.charAt (i); } OS.CPSSetProcessName (psn, buffer); OS.CPSEnableForegroundOperation (psn, 0x03, 0x3C, 0x2C, 0x1103); OS.SetFrontProcess (psn); } queue = OS.GetCurrentEventQueue(); { /* Shell */ int windowAttrs = OS.kWindowCompositingAttribute | OS.kWindowCloseBoxAttribute | OS.kWindowFullZoomAttribute | OS.kWindowCollapseBoxAttribute | OS.kWindowResizableAttribute | OS.kWindowStandardHandlerAttribute; Rect r = new Rect(); OS.SetRect(r, (short)100, (short)100, (short)800, (short)800); int[] window = new int[1]; OS.CreateNewWindow(OS.kDocumentWindowClass, windowAttrs, r, window); shellHandle = window[0]; Callback windowCallback = new Callback(this, "windowEventHandler", 3); int[] eventTypes = new int[] { OS.kEventClassWindow, OS.kEventWindowClose, }; int windowEventTarget = OS.GetWindowEventTarget(shellHandle); OS.InstallEventHandler(windowEventTarget, windowCallback.getAddress(), eventTypes.length / 2, eventTypes, 0, null); int[] root = new int[1]; OS.HIViewFindByID(OS.HIViewGetRoot(shellHandle), OS.kHIViewWindowContentID(), root); rootHandle = root[0]; } { /* Carbon Button 1 */ Rect r = new Rect(); r.left = (short)10; r.top = (short)10; r.right = (short)200; r.bottom = (short)100; int outControl[] = new int[1]; OS.CreatePushButtonControl (shellHandle, r, 0, outControl); carbonButton1 = outControl[0]; OS.HIViewAddSubview(rootHandle, carbonButton1); char [] buffer = new char [] {'C','a','r','b','o','n',' ','B','u','t','t','o','n',' ','1'}; int ptr = OS.CFStringCreateWithCharacters (OS.kCFAllocatorDefault, buffer, buffer.length); OS.SetControlTitleWithCFString (carbonButton1, ptr); OS.CFRelease (ptr); } { /* Carbon Button 2 */ Rect r = new Rect(); r.left = (short)220; r.top = (short)10; r.right = (short)410; r.bottom = (short)100; int outControl[] = new int[1]; OS.CreatePushButtonControl (shellHandle, r, 0, outControl); carbonButton2 = outControl[0]; OS.HIViewAddSubview(rootHandle, carbonButton2); char [] buffer = new char [] {'C','a','r','b','o','n',' ','B','u','t','t','o','n',' ','2'}; int ptr = OS.CFStringCreateWithCharacters (OS.kCFAllocatorDefault, buffer, buffer.length); OS.SetControlTitleWithCFString (carbonButton2, ptr); OS.CFRelease (ptr); } if (createCocoaButtons) { /* Cocoa Button 1 */ int embedHandle = Cocoa.objc_msgSend(Cocoa.objc_msgSend(Cocoa.objc_getClass("NSButton"), Cocoa.S_alloc), Cocoa.S_initWithFrame, 0); int outControl[] = new int[1]; Cocoa.HICocoaViewCreate(embedHandle, 0, outControl); /* OSX >= 10.5 */ cocoaButton1 = outControl[0]; OS.HIViewAddSubview(rootHandle, cocoaButton1); OS.HIViewSetVisible(cocoaButton1, true); CGRect rect = new CGRect(); rect.x = 10; rect.y = 120; rect.width = 190; rect.height = 90; OS.HIViewSetFrame(cocoaButton1, rect); } if (createCocoaButtons) { /* Cocoa Button 2 */ int embedHandle = Cocoa.objc_msgSend(Cocoa.objc_msgSend(Cocoa.objc_getClass("NSButton"), Cocoa.S_alloc), Cocoa.S_initWithFrame, 0); int outControl[] = new int[1]; Cocoa.HICocoaViewCreate(embedHandle, 0, outControl); /* OSX >= 10.5 */ cocoaButton2 = outControl[0]; OS.HIViewAddSubview(rootHandle, cocoaButton2); OS.HIViewSetVisible(cocoaButton2, true); CGRect rect = new CGRect(); rect.x = 220; rect.y = 120; rect.width = 190; rect.height = 90; OS.HIViewSetFrame(cocoaButton2, rect); } if (createBrowser) { /* Browser */ int outControl[] = new int[1]; Cocoa.HIWebViewCreate(outControl); webViewHandle = outControl[0]; OS.HIViewAddSubview(rootHandle, webViewHandle); OS.HIViewSetVisible(webViewHandle, true); CGRect rect = new CGRect(); rect.x = 10; rect.y = 230; rect.width = 400; rect.height = 300; OS.HIViewSetFrame(webViewHandle, rect); final int webView = Cocoa.HIWebViewGetWebView(webViewHandle); String url = "http://www.google.com"; int length = url.length(); char[] buffer = new char[length]; url.getChars(0, length, buffer, 0); int sHandle = OS.CFStringCreateWithCharacters(0, buffer, length); int inURL= Cocoa.objc_msgSend(Cocoa.C_NSURL, Cocoa.S_URLWithString, sHandle); OS.CFRelease(sHandle); int request= Cocoa.objc_msgSend(Cocoa.C_NSURLRequest, Cocoa.S_requestWithURL, inURL); int mainFrame= Cocoa.objc_msgSend(webView, Cocoa.S_mainFrame); Cocoa.objc_msgSend(mainFrame, Cocoa.S_loadRequest, request); } OS.ShowWindow (shellHandle); OS.SetKeyboardFocus(shellHandle, carbonButton1, (short)-1); int[] eventRef = new int[1]; int eventTargetRef = OS.GetEventDispatcherTarget(); while (running) { while (running && (OS.ReceiveNextEvent(0, null, OS.kEventDurationForever, true, eventRef)) == OS.noErr) { OS.SendEventToEventTarget (eventRef[0], eventTargetRef); OS.ReleaseEvent(eventRef[0]); } } OS.DisposeWindow(shellHandle); } int windowEventHandler (int eventHandlerCallRef, int eventRef, int userData) { int eventClass = OS.GetEventClass(eventRef); int eventKind = OS.GetEventKind(eventRef); switch (eventClass) { case OS.kEventClassWindow: switch(eventKind) { case OS.kEventWindowClose: running = false; break; } break; } return OS.CallNextEventHandler(eventHandlerCallRef, eventRef); } }
This is a real bad one. Scott, anyone at Apple know about this?
Please file a bug if you haven't done so already. HIWebViewCreate creates a shadow NSWindow because the underlying WebView eventually needs an NSWindow for event handling, coordinate translation, and so on. I suspect there's no easy way to work around it.
It works on Tiger, right Carolyn?
Confirmed that it works correctly on Tiger. i.e. VoiceOver and other accessible tools are not confused by the presence of a Browser on Tiger.
Opened Apple Bug ID# 5782404
I'm trying to reproduce this bug with the above sample in it's own class file and org.eclipse.swt.carbon.macosx_3.3.2.v3347a.jar org.eclipse.ui.carbon_3.2.100.I20070605-0010.jar on my classpath, and it only hangs. Would it be possible to get a native C test case for this bug, since that would help move this bug along quicker? Thanks.
I am at home at the moment (and my Mac is at work) so I can't test this theory, but did you start java with -XstartOnFirstThread ? Here's how I start the SWT ControlExample as a Mac application (using the SWT jar and library that are in my workspace, but you can change your classpath and library path to point to your locations). 1) The following command line is in a file called Contents/MacOS/ControlExample: #!/bin/sh WORKSPACE=/Users/cmacleod/Documents/workspace exec java \ -XstartOnFirstThread \ -classpath $WORKSPACE/org.eclipse.swt/bin:$WORKSPACE/org.eclipse.swt.examples/bin \ -Djava.library.path=$WORKSPACE/org.eclipse.swt.carbon.macosx \ org.eclipse.swt.examples.controlexample.ControlExample 2) And here's the Contents/Info.plist file: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleExecutable</key> <string>ControlExample</string> <key>CFBundleGetInfoString</key> <string>SWT ControlExample</string> <key>CFBundleIconFile</key> <string>GenericJavaApp.icns</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>ControlExample</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleSignature</key> <string>?????</string> <key>CFBundleVersion</key> <string>1.0</string> <key>NSPrincipalClass</key> <string>NSApplication</string> </dict> </plist> 3) The GenericJavaApp.icns is just some random icon (any icon will do), and it is in the Contents/Resources directory. Hopefully this will help you run the PI code if you change "ControlExample" to "CarbonPI" in the batch file and the Info.plist file. I will try to get the native C test case going tomorrow, but it may have to wait until next week when Steve & Silenio return from EclipseCon.
We found a work-around, so we are going to mark this fixed at our end. (i.e. the problem still exists, so I will leave the Mac bug open). Believe it or not, we just need to create and dispose an NSButton for every Window that contains a WebView (aka SWT Browser). Then the VoiceOver problem just goes away... <grin> Creating an NSButton must initialize some global state in Window that a WebView needs in order to work well with VoiceOver. :)