| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "AccessibilityController.h" |
| |
| #import "AccessibilityCommonCocoa.h" |
| #import "AccessibilityNotificationHandler.h" |
| #import "InjectedBundle.h" |
| #import "InjectedBundlePage.h" |
| #import "JSBasics.h" |
| #import <JavaScriptCore/JSStringRefCF.h> |
| #import <WebKit/WKBundle.h> |
| #import <WebKit/WKBundleFramePrivate.h> |
| #import <WebKit/WKBundlePage.h> |
| #import <WebKit/WKBundlePagePrivate.h> |
| |
| #import <pal/spi/mac/HIServicesSPI.h> |
| |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| #import <pal/spi/cocoa/AccessibilitySupportSPI.h> |
| #endif |
| |
| namespace WTR { |
| |
| void AccessibilityController::platformInitialize() |
| { |
| // Override the client identifier to be kAXClientTypeWebKitTesting which is treated the same as the VoiceOver identifier in isolated tree mode. |
| // This also allows to enable some APIs during testing only for unit test purposes, not for other clients consumption. |
| _AXSetClientIdentificationOverride((AXClientType)kAXClientTypeWebKitTesting); |
| } |
| |
| RefPtr<AccessibilityUIElement> AccessibilityController::focusedElement(JSContextRef context) |
| { |
| if (!_WKAccessibilityRootObjectForTesting(WKBundleFrameForJavaScriptContext(context))) |
| return nullptr; |
| |
| RetainPtr<PlatformUIElement> focus; |
| executeOnAXThreadAndWait([&focus] () { |
| focus = static_cast<PlatformUIElement>(WKAccessibilityFocusedUIElement()); |
| }); |
| if (focus) |
| return AccessibilityUIElement::create(focus.get()); |
| return nullptr; |
| } |
| |
| bool AccessibilityController::addNotificationListener(JSContextRef context, JSValueRef functionCallback) |
| { |
| if (!functionCallback) |
| return false; |
| |
| if (m_globalNotificationHandler) |
| return false; |
| |
| m_globalNotificationHandler = adoptNS([[AccessibilityNotificationHandler alloc] initWithContext:context]); |
| [m_globalNotificationHandler.get() setCallback:functionCallback]; |
| [m_globalNotificationHandler.get() startObserving]; |
| |
| return true; |
| } |
| |
| bool AccessibilityController::removeNotificationListener() |
| { |
| ASSERT(m_globalNotificationHandler); |
| |
| [m_globalNotificationHandler.get() stopObserving]; |
| m_globalNotificationHandler.clear(); |
| |
| return true; |
| } |
| |
| void AccessibilityController::resetToConsistentState() |
| { |
| if (m_globalNotificationHandler) |
| removeNotificationListener(); |
| } |
| |
| static id findAccessibleObjectById(id obj, NSString *idAttribute) |
| { |
| BEGIN_AX_OBJC_EXCEPTIONS |
| id objIdAttribute = [obj accessibilityAttributeValue:@"AXDOMIdentifier"]; |
| if ([objIdAttribute isKindOfClass:[NSString class]] && [objIdAttribute isEqualToString:idAttribute]) |
| return obj; |
| END_AX_OBJC_EXCEPTIONS |
| |
| BEGIN_AX_OBJC_EXCEPTIONS |
| NSArray *children = [obj accessibilityAttributeValue:NSAccessibilityChildrenAttribute]; |
| NSUInteger childrenCount = [children count]; |
| for (NSUInteger i = 0; i < childrenCount; ++i) { |
| id result = findAccessibleObjectById([children objectAtIndex:i], idAttribute); |
| if (result) |
| return result; |
| } |
| END_AX_OBJC_EXCEPTIONS |
| |
| return nil; |
| } |
| |
| void AccessibilityController::injectAccessibilityPreference(JSStringRef domain, JSStringRef key, JSStringRef value) |
| { |
| auto page = InjectedBundle::singleton().page()->page(); |
| NSNumber *numberValue = @([[NSString stringWithJSStringRef:value] integerValue]); |
| NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:numberValue requiringSecureCoding:YES error:nil]; |
| NSString *encodedString = [encodedData base64EncodedStringWithOptions:0]; |
| WKAccessibilityTestingInjectPreference(page, toWK(domain).get(), toWK(key).get(), toWK(encodedString).get()); |
| } |
| |
| RefPtr<AccessibilityUIElement> AccessibilityController::accessibleElementById(JSContextRef context, JSStringRef idAttribute) |
| { |
| PlatformUIElement root = static_cast<PlatformUIElement>(_WKAccessibilityRootObjectForTesting(WKBundleFrameForJavaScriptContext(context))); |
| |
| NSString *attributeName = [NSString stringWithJSStringRef:idAttribute]; |
| RetainPtr<id> result; |
| executeOnAXThreadAndWait([&root, &attributeName, &result] { |
| result = findAccessibleObjectById(root, attributeName); |
| }); |
| |
| if (result) |
| return AccessibilityUIElement::create(result.get()); |
| return nullptr; |
| } |
| |
| JSRetainPtr<JSStringRef> AccessibilityController::platformName() |
| { |
| return WTR::createJSString("mac"); |
| } |
| |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| void AccessibilityController::updateIsolatedTreeMode() |
| { |
| _AXSSetIsolatedTreeMode(m_accessibilityIsolatedTreeMode ? AXSIsolatedTreeModeSecondaryThread : AXSIsolatedTreeModeOff); |
| } |
| #endif |
| |
| void AccessibilityController::overrideClient(JSStringRef clientType) |
| { |
| NSString *clientString = [NSString stringWithJSStringRef:clientType]; |
| if ([clientString caseInsensitiveCompare:@"voiceover"] == NSOrderedSame) |
| _AXSetClientIdentificationOverride(kAXClientTypeVoiceOver); |
| else |
| _AXSetClientIdentificationOverride(kAXClientTypeNoActiveRequestFound); |
| } |
| |
| void AccessibilityController::printTrees(JSContextRef context) |
| { |
| PlatformUIElement root = static_cast<PlatformUIElement>(_WKAccessibilityRootObjectForTesting(WKBundleFrameForJavaScriptContext(context))); |
| [root accessibilityPerformAction:@"AXLogTrees"]; |
| } |
| |
| // AXThread implementation |
| |
| void AXThread::initializeRunLoop() |
| { |
| // Initialize the run loop. |
| { |
| Locker locker { m_initializeRunLoopMutex }; |
| |
| m_threadRunLoop = CFRunLoopGetCurrent(); |
| |
| CFRunLoopSourceContext context = { 0, this, 0, 0, 0, 0, 0, 0, 0, threadRunLoopSourceCallback }; |
| m_threadRunLoopSource = adoptCF(CFRunLoopSourceCreate(0, 0, &context)); |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), m_threadRunLoopSource.get(), kCFRunLoopDefaultMode); |
| |
| m_initializeRunLoopConditionVariable.notifyAll(); |
| } |
| |
| ASSERT(isCurrentThread()); |
| |
| CFRunLoopRun(); |
| } |
| |
| void AXThread::wakeUpRunLoop() |
| { |
| CFRunLoopSourceSignal(m_threadRunLoopSource.get()); |
| CFRunLoopWakeUp(m_threadRunLoop.get()); |
| } |
| |
| void AXThread::threadRunLoopSourceCallback(void* axThread) |
| { |
| static_cast<AXThread*>(axThread)->threadRunLoopSourceCallback(); |
| } |
| |
| void AXThread::threadRunLoopSourceCallback() |
| { |
| @autoreleasepool { |
| dispatchFunctionsFromAXThread(); |
| } |
| } |
| |
| } // namespace WTR |