Run AccessibilityController::rootElement on secondary thread to simulate HIServices...
authorandresg_22@apple.com <andresg_22@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Nov 2019 01:12:00 +0000 (01:12 +0000)
committerandresg_22@apple.com <andresg_22@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Nov 2019 01:12:00 +0000 (01:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=204226

Reviewed by Chris Fleizach.

Source/WebCore:

No new tests, no new functionality.

Added AXObjectCahce::canUseSecondaryAXThread to support
WTR::AccessibilityController spawning of a secondary thread to simulate
execution of isolated tree code.

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::canUseSecondaryAXThread):
* accessibility/AXObjectCache.h:

Source/WebKit:

Added WKAccessibilityCanUseSecondaryAXThread to support
WTR::AccessibilityController spawning of a secondary thread to simulate
execution of isolated tree code.

* WebProcess/InjectedBundle/API/c/WKBundlePage.cpp:
(WKAccessibilityCanUseSecondaryAXThread):
* WebProcess/InjectedBundle/API/c/WKBundlePagePrivate.h:

Tools:

HIServices _AXUIElementUseSecondaryAXThread and _AXUIElementRequestServicedBySecondaryAXThread
do not work for WebKitTestRunner since this is calling directly into
WebCore/accessibility via JavaScript without going through HIServices.
Thus to simulate the behavior of HIServices, AccessibilityController is
spawning a secondary thread to service the JavaScript requests.
The very first request needs to be serviced in the main thread,
and all subsequent requests in the secondary thread. this is what
the behavior would be if using HIServices.
Once the IsolatedTree would be enabled, the first request has to be
served in the main thread in order to build the AXIsolatedTree.

* WebKitTestRunner/InjectedBundle/AccessibilityController.cpp:
(WTR::AccessibilityController::rootElement):
(WTR::AXThread::AXThread):
(WTR::AXThread::isCurrentThread):
(WTR::AXThread::dispatch):
(WTR::AXThread::dispatchBarrier):
(WTR::AXThread::singleton):
(WTR::AXThread::createThreadIfNeeded):
(WTR::AXThread::dispatchFunctionsFromAXThread):
(WTR::AXThread::initializeRunLoop):
(WTR::AXThread::wakeUpRunLoop):
(WTR::AXThread::threadRunLoopSourceCallback):
* WebKitTestRunner/InjectedBundle/AccessibilityController.h:
* WebKitTestRunner/InjectedBundle/mac/AccessibilityControllerMac.mm:
(WTR::AXThread::initializeRunLoop):
(WTR::AXThread::wakeUpRunLoop):
(WTR::AXThread::threadRunLoopSourceCallback):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@252609 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/accessibility/AXObjectCache.cpp
Source/WebCore/accessibility/AXObjectCache.h
Source/WebKit/ChangeLog
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePagePrivate.h
Tools/ChangeLog
Tools/WebKitTestRunner/InjectedBundle/AccessibilityController.cpp
Tools/WebKitTestRunner/InjectedBundle/AccessibilityController.h
Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityControllerMac.mm

index 5c31069..960be01 100644 (file)
@@ -1,3 +1,20 @@
+2019-11-18  Andres Gonzalez  <andresg_22@apple.com>
+
+        Run AccessibilityController::rootElement on secondary thread to simulate HIServices during LayoutTests.
+        https://bugs.webkit.org/show_bug.cgi?id=204226
+
+        Reviewed by Chris Fleizach.
+
+        No new tests, no new functionality.
+
+        Added AXObjectCahce::canUseSecondaryAXThread to support
+        WTR::AccessibilityController spawning of a secondary thread to simulate
+        execution of isolated tree code.
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::canUseSecondaryAXThread):
+        * accessibility/AXObjectCache.h:
+
 2019-11-15  Ryosuke Niwa  <rniwa@webkit.org>
 
         Share more code between WindowEventLoop and WorkerEventLoop by introducing EventLoopTaskGroup
index 367eab8..e2ac72a 100644 (file)
@@ -705,6 +705,21 @@ AXCoreObject* AXObjectCache::isolatedTreeRootObject()
 }
 #endif
 
+bool AXObjectCache::canUseSecondaryAXThread()
+{
+#if ENABLE(ACCESSIBILITY_ISOLATED_TREE) && PLATFORM(MAC)
+    if (_AXUIElementRequestServicedBySecondaryAXThread())
+        return true;
+
+    // _AXUIElementRequestServicedBySecondaryAXThread returns false for
+    // LayoutTests, but we still want to run LayoutTests using isolated tree on
+    // a secondary thread to simulate the actual execution.
+    return clientSupportsIsolatedTree();
+#else
+    return false;
+#endif
+}
+
 AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame)
 {
     if (!gAccessibilityEnabled)
index 97a48d4..c013ec0 100644 (file)
@@ -200,6 +200,8 @@ private:
 #endif
 
 public:
+    WEBCORE_EXPORT bool canUseSecondaryAXThread();
+
 #if ENABLE(ACCESSIBILITY)
     WEBCORE_EXPORT static void enableAccessibility();
     WEBCORE_EXPORT static void disableAccessibility();
index c5b97ed..dc5efa1 100644 (file)
@@ -1,3 +1,18 @@
+2019-11-18  Andres Gonzalez  <andresg_22@apple.com>
+
+        Run AccessibilityController::rootElement on secondary thread to simulate HIServices during LayoutTests.
+        https://bugs.webkit.org/show_bug.cgi?id=204226
+
+        Reviewed by Chris Fleizach.
+
+        Added WKAccessibilityCanUseSecondaryAXThread to support
+        WTR::AccessibilityController spawning of a secondary thread to simulate
+        execution of isolated tree code.
+
+        * WebProcess/InjectedBundle/API/c/WKBundlePage.cpp:
+        (WKAccessibilityCanUseSecondaryAXThread):
+        * WebProcess/InjectedBundle/API/c/WKBundlePagePrivate.h:
+
 2019-11-18  Chris Dumez  <cdumez@apple.com>
 
         Promote main thread assertions related to sendWithAsyncReply() to be release assertions
index 2668374..efb3694 100644 (file)
@@ -282,6 +282,33 @@ void* WKAccessibilityFocusedObject(WKBundlePageRef pageRef)
 #endif
 }
 
+bool WKAccessibilityCanUseSecondaryAXThread(WKBundlePageRef pageRef)
+{
+#if ENABLE(ACCESSIBILITY)
+    if (!pageRef)
+        return false;
+
+    WebCore::Page* page = WebKit::toImpl(pageRef)->corePage();
+    if (!page)
+        return false;
+
+    WebCore::Frame& core = page->mainFrame();
+    if (!core.document())
+        return false;
+
+    WebCore::AXObjectCache::enableAccessibility();
+
+    auto* axObjectCache = core.document()->axObjectCache();
+    if (!axObjectCache)
+        return false;
+
+    return axObjectCache->canUseSecondaryAXThread();
+#else
+    UNUSED_PARAM(pageRef);
+    return false;
+#endif
+}
+
 void WKAccessibilityEnableEnhancedAccessibility(bool enable)
 {
 #if ENABLE(ACCESSIBILITY)
index 3a5eb9e..3a98814 100644 (file)
@@ -96,6 +96,7 @@ WK_EXPORT bool WKBundlePageCanShowMIMEType(WKBundlePageRef, WKStringRef mimeType
 
 WK_EXPORT void* WKAccessibilityRootObject(WKBundlePageRef);
 WK_EXPORT void* WKAccessibilityFocusedObject(WKBundlePageRef);
+WK_EXPORT bool WKAccessibilityCanUseSecondaryAXThread(WKBundlePageRef);
 
 WK_EXPORT void WKAccessibilityEnableEnhancedAccessibility(bool);
 WK_EXPORT bool WKAccessibilityEnhancedAccessibilityEnabled();
index 7da3e8e..3f792b0 100644 (file)
@@ -1,3 +1,39 @@
+2019-11-18  Andres Gonzalez  <andresg_22@apple.com>
+
+        Run AccessibilityController::rootElement on secondary thread to simulate HIServices during LayoutTests.
+        https://bugs.webkit.org/show_bug.cgi?id=204226
+
+        Reviewed by Chris Fleizach.
+
+        HIServices _AXUIElementUseSecondaryAXThread and _AXUIElementRequestServicedBySecondaryAXThread
+        do not work for WebKitTestRunner since this is calling directly into
+        WebCore/accessibility via JavaScript without going through HIServices.
+        Thus to simulate the behavior of HIServices, AccessibilityController is
+        spawning a secondary thread to service the JavaScript requests.
+        The very first request needs to be serviced in the main thread,
+        and all subsequent requests in the secondary thread. this is what
+        the behavior would be if using HIServices.
+        Once the IsolatedTree would be enabled, the first request has to be
+        served in the main thread in order to build the AXIsolatedTree.
+
+        * WebKitTestRunner/InjectedBundle/AccessibilityController.cpp:
+        (WTR::AccessibilityController::rootElement):
+        (WTR::AXThread::AXThread):
+        (WTR::AXThread::isCurrentThread):
+        (WTR::AXThread::dispatch):
+        (WTR::AXThread::dispatchBarrier):
+        (WTR::AXThread::singleton):
+        (WTR::AXThread::createThreadIfNeeded):
+        (WTR::AXThread::dispatchFunctionsFromAXThread):
+        (WTR::AXThread::initializeRunLoop):
+        (WTR::AXThread::wakeUpRunLoop):
+        (WTR::AXThread::threadRunLoopSourceCallback):
+        * WebKitTestRunner/InjectedBundle/AccessibilityController.h:
+        * WebKitTestRunner/InjectedBundle/mac/AccessibilityControllerMac.mm:
+        (WTR::AXThread::initializeRunLoop):
+        (WTR::AXThread::wakeUpRunLoop):
+        (WTR::AXThread::threadRunLoopSourceCallback):
+
 2019-11-18  Jonathan Bedard  <jbedard@apple.com>
 
         results.webkit.org: Uncollapsed failures shouldn't include runs with no failures
index bedb3c4..236bcab 100644 (file)
@@ -76,9 +76,25 @@ bool AccessibilityController::enhancedAccessibilityEnabled()
 Ref<AccessibilityUIElement> AccessibilityController::rootElement()
 {
     WKBundlePageRef page = InjectedBundle::singleton().page()->page();
-    void* root = WKAccessibilityRootObject(page);
-    
-    return AccessibilityUIElement::create(static_cast<PlatformUIElement>(root));    
+    PlatformUIElement root = nullptr;
+
+    if (m_useAXThread) {
+        AXThread::dispatch([&root, page, this] {
+            root = static_cast<PlatformUIElement>(WKAccessibilityRootObject(page));
+            m_semaphore.signal();
+        });
+
+        m_semaphore.wait();
+    } else {
+        root = static_cast<PlatformUIElement>(WKAccessibilityRootObject(page));
+
+        if (WKAccessibilityCanUseSecondaryAXThread(page)) {
+            // Set m_useAXThread to true for next request.
+            m_useAXThread = true;
+        }
+    }
+
+    return AccessibilityUIElement::create(root);
 }
 
 Ref<AccessibilityUIElement> AccessibilityController::focusedElement()
@@ -96,6 +112,102 @@ RefPtr<AccessibilityUIElement> AccessibilityController::elementAtPoint(int x, in
     return uiElement->elementAtPoint(x, y);
 }
 
+#if PLATFORM(COCOA)
+
+// AXThread implementation
+
+AXThread::AXThread()
+{
+}
+
+bool AXThread::isCurrentThread()
+{
+    return AXThread::singleton().m_thread == &Thread::current();
+}
+
+void AXThread::dispatch(Function<void()>&& function)
+{
+    auto& axThread = AXThread::singleton();
+    axThread.createThreadIfNeeded();
+
+    {
+        std::lock_guard<Lock> lock(axThread.m_functionsMutex);
+        axThread.m_functions.append(WTFMove(function));
+    }
+
+    axThread.wakeUpRunLoop();
+}
+
+void AXThread::dispatchBarrier(Function<void()>&& function)
+{
+    dispatch([function = WTFMove(function)]() mutable {
+        callOnMainThread(WTFMove(function));
+    });
+}
+
+AXThread& AXThread::singleton()
+{
+    static NeverDestroyed<AXThread> axThread;
+    return axThread;
+}
+
+void AXThread::createThreadIfNeeded()
+{
+    // Wait for the thread to initialize the run loop.
+    std::unique_lock<Lock> lock(m_initializeRunLoopMutex);
+
+    if (!m_thread) {
+        m_thread = Thread::create("WKTR: AccessibilityController", [this] {
+            WTF::Thread::setCurrentThreadIsUserInteractive();
+            initializeRunLoop();
+        });
+    }
+
+    m_initializeRunLoopConditionVariable.wait(lock, [this] {
+#if PLATFORM(COCOA)
+        return m_threadRunLoop;
+#else
+        return m_runLoop;
+#endif
+    });
+}
+
+void AXThread::dispatchFunctionsFromAXThread()
+{
+    ASSERT(isCurrentThread());
+
+    Vector<Function<void()>> functions;
+
+    {
+        std::lock_guard<Lock> lock(m_functionsMutex);
+        functions = WTFMove(m_functions);
+    }
+
+    for (auto& function : functions)
+        function();
+}
+
+#if !PLATFORM(MAC)
+NO_RETURN_DUE_TO_ASSERT void AXThread::initializeRunLoop()
+{
+    ASSERT_NOT_REACHED();
+}
+
+void AXThread::wakeUpRunLoop()
+{
+}
+
+void AXThread::threadRunLoopSourceCallback(void*)
+{
+}
+
+void AXThread::threadRunLoopSourceCallback()
+{
+}
+#endif // !PLATFORM(MAC)
+
+#endif // PLATFORM(COCOA)
+
 } // namespace WTR
 #endif // ENABLE(ACCESSIBILITY)
 
index 22ac947..4f61ebd 100644 (file)
 #include "AccessibilityUIElement.h"
 #include "JSWrappable.h"
 #include <JavaScriptCore/JSObjectRef.h>
+#include <wtf/Condition.h>
 #include <wtf/FastMalloc.h>
 #include <wtf/Platform.h>
+#include <wtf/Threading.h>
+#include <wtf/threads/BinarySemaphore.h>
+
 #if USE(ATK)
 #include "AccessibilityNotificationHandlerAtk.h"
 #endif
@@ -82,6 +86,70 @@ private:
 #elif USE(ATK)
     RefPtr<AccessibilityNotificationHandler> m_globalNotificationHandler;
 #endif
+
+#if PLATFORM(COCOA)
+    // _AXUIElementUseSecondaryAXThread and _AXUIElementRequestServicedBySecondaryAXThread
+    // do not work for WebKitTestRunner since this is calling directly into
+    // WebCore/accessibility via JavaScript without going through HIServices.
+    // Thus to simulate the behavior of HIServices, AccessibilityController is spawning a secondary thread to service the JavaScript requests.
+    // The following flag allows to run the very first request in the main
+    // thread and all subsequent requests in the secondary thread. this is what
+    // the behavior would be if using HIServices.
+    // The first request has to be served in the main thread in order to build
+    // the AXIsolatedTree.
+    bool m_useAXThread { false };
+    BinarySemaphore m_semaphore;
+#endif
 };
 
+#if PLATFORM(COCOA)
+
+class AXThread {
+    WTF_MAKE_NONCOPYABLE(AXThread);
+
+public:
+    static bool isCurrentThread();
+    static void dispatch(Function<void()>&&);
+
+    // Will dispatch the given function on the main thread once all pending functions
+    // on the AX thread have finished executing. Used for synchronization purposes.
+    static void dispatchBarrier(Function<void()>&&);
+
+private:
+    friend NeverDestroyed<AXThread>;
+
+    AXThread();
+
+    static AXThread& singleton();
+
+    void createThreadIfNeeded();
+    void dispatchFunctionsFromAXThread();
+
+    void initializeRunLoop();
+    void wakeUpRunLoop();
+
+#if PLATFORM(COCOA)
+    static void threadRunLoopSourceCallback(void* AXThread);
+    void threadRunLoopSourceCallback();
+#endif
+
+    RefPtr<Thread> m_thread;
+
+    Condition m_initializeRunLoopConditionVariable;
+    Lock m_initializeRunLoopMutex;
+
+    Lock m_functionsMutex;
+    Vector<Function<void()>> m_functions;
+
+#if PLATFORM(COCOA)
+    // FIXME: We should use WebCore::RunLoop here.
+    RetainPtr<CFRunLoopRef> m_threadRunLoop;
+    RetainPtr<CFRunLoopSourceRef> m_threadRunLoopSource;
+#else
+    RunLoop* m_runLoop { nullptr };
+#endif
+};
+
+#endif // PLATFORM(COCOA)
+
 } // namespace WTR
index 90e8eef..d1dd228 100644 (file)
@@ -109,4 +109,44 @@ JSRetainPtr<JSStringRef> AccessibilityController::platformName()
     return adopt(JSStringCreateWithUTF8CString("mac"));
 }
 
+// AXThread implementation
+
+void AXThread::initializeRunLoop()
+{
+    // Initialize the run loop.
+    {
+        std::lock_guard<Lock> lock(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