[GTK] REGRESSION(r243860): Many tests failing
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Apr 2019 08:46:57 +0000 (08:46 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Apr 2019 08:46:57 +0000 (08:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=196791

Reviewed by Joanmarie Diggs.

Source/WebKit:

Calling updateAccessibilityTree() on document loaded was causing a re-layout because of the backing store update
that confused all those tests. We shouldn't need to update the accessibility tree on document load, it should
happen automatically when root object is attached/detached. This patch emits children-changed::add when the root
object wrapper is attached and children-changed::remove when the root object is detached. That way ATs are
notified of the changes in the accessibility tree.

* WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
(WebKit::WebFrameLoaderClient::dispatchDidFinishDocumentLoad): Remove call to WebPage::updateAccessibilityTree().
* WebProcess/WebPage/WebPage.h: Remove updateAccessibilityTree().
* WebProcess/WebPage/atk/WebKitWebPageAccessibilityObject.cpp:
(coreRootObjectWrapperDetachedCallback): Emit children-changed::remove.
(rootWebAreaWrapper): Helper to get the root WebArea wrapper.
(accessibilityRootObjectWrapper): Set the parent here when root object is created and emit children-changed::add.
(webkitWebPageAccessibilityObjectRefChild): Dot no set the parent here, it's now set when the root object is created.
* WebProcess/WebPage/atk/WebKitWebPageAccessibilityObject.h: Remove webkitWebPageAccessibilityObjectRefresh().
* WebProcess/WebPage/gtk/WebPageGtk.cpp:

Tools:

Rework the accessibility unit test to use DBus for the communication with the server. This way we can load
multiple documents and check that accessibility hierarchy is updated after a navigation.

* TestWebKitAPI/Tests/WebKitGtk/AccessibilityTestServer.cpp:
(loadChangedCallback):
* TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
(AccessibilityTest::AccessibilityTest):
(AccessibilityTest::~AccessibilityTest):
(AccessibilityTest::loadHTMLAndWaitUntilFinished):
(AccessibilityTest::findTestServerApplication):
(AccessibilityTest::findDocumentWeb):
(AccessibilityTest::findRootObject):
(AccessibilityTest::waitUntilChildrenRemoved):
(AccessibilityTest::ensureProxy):
(testAtspiBasicHierarchy):
(beforeAll):
(afterAll):

LayoutTests:

Remove expectations for tests that pass now.

* platform/gtk/TestExpectations:

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

LayoutTests/ChangeLog
LayoutTests/platform/gtk/TestExpectations
Source/WebKit/ChangeLog
Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/atk/WebKitWebPageAccessibilityObject.cpp
Source/WebKit/WebProcess/WebPage/atk/WebKitWebPageAccessibilityObject.h
Source/WebKit/WebProcess/WebPage/gtk/WebPageGtk.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitGtk/AccessibilityTestServer.cpp
Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp

index cf0e853..9033bfe 100644 (file)
@@ -1,3 +1,14 @@
+2019-04-11  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] REGRESSION(r243860): Many tests failing
+        https://bugs.webkit.org/show_bug.cgi?id=196791
+
+        Reviewed by Joanmarie Diggs.
+
+        Remove expectations for tests that pass now.
+
+        * platform/gtk/TestExpectations:
+
 2019-04-11  Megan Gardner  <megan_gardner@apple.com>
 
         Fix flaky LayoutTests/fast/events/autoscroll-when-input-is-offscreen.html
index 0097f4c..51975a9 100644 (file)
@@ -1893,7 +1893,7 @@ webkit.org/b/179174 inspector/console/webcore-logging.html [ Failure Crash ]
 
 webkit.org/b/181030 wasm/iframe-postmessage.html [ Pass Failure ]
 webkit.org/b/179948 [ Release ] fast/hidpi/filters-reference.html [ Pass ImageOnlyFailure ]
-webkit.org/b/196791 webkit.org/b/181031 fast/frames/crash-when-iframe-is-remove-in-eventhandler.html [ Pass Crash Failure ]
+webkit.org/b/181031 fast/frames/crash-when-iframe-is-remove-in-eventhandler.html [ Pass Crash ]
 
 webkit.org/b/181528 http/tests/cache/memory-cache-pruning.html [ Pass Crash ]
 
@@ -3817,49 +3817,6 @@ webkit.org/b/116977 media/event-attributes.html [ Failure ]
 
 webkit.org/b/196541 editing/pasteboard/paste-content-with-overflow-auto-parent-across-origin.html [ Failure ]
 
-webkit.org/b/196791 tables/mozilla/dom/deleteCol1.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/deleteCol2.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/deleteCol3.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/deleteColGroup1.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/deleteColGroup2.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertColGroups1.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertColGroups2.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertCols1.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertCols2.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertCols3.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertCols4.html [ Failure ]
-webkit.org/b/196791 tables/mozilla/dom/insertCols5.html [ Failure ]
-webkit.org/b/196791 svg/hixie/text/003.html [ Failure ]
-webkit.org/b/196791 svg/custom/linking-uri-01-b.svg [ Failure ]
-webkit.org/b/196791 imported/w3c/web-platform-tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html [ Failure ]
-webkit.org/b/196791 fast/text/international/bidi-listbox-atsui.html [ Failure ]
-webkit.org/b/196791 fast/text/international/bidi-innertext.html [ Failure ]
-webkit.org/b/196791 fast/text/international/bidi-L2-run-reordering.html [ Failure ]
-webkit.org/b/196791 fast/scrolling/scroll-animator-select-list-events.html [ Failure ]
-webkit.org/b/196791 fast/scrolling/scroll-animator-overlay-scrollbars-hovered.html [ Failure ]
-webkit.org/b/196791 fast/scrolling/scroll-animator-overlay-scrollbars-clicked.html [ Failure ]
-webkit.org/b/196791 fast/scrolling/scroll-animator-basic-events.html [ Failure ]
-webkit.org/b/196791 fast/frames/inline-object-inside-frameset.html [ Failure ]
-webkit.org/b/196791 fast/forms/visual-hebrew-text-field.html [ Failure ]
-webkit.org/b/196791 fast/forms/select-visual-hebrew.html [ Failure ]
-webkit.org/b/196791 fast/forms/form-submission-crash-3.html [ Failure ]
-webkit.org/b/196791 compositing/backing/backing-store-attachment-empty-keyframe.html [ Failure ]
-webkit.org/b/196791 fast/images/animated-gif-no-layout.html [ Failure ]
-webkit.org/b/196791 http/tests/incremental/stylesheet-body-incremental-rendering.html [ Failure ]
-webkit.org/b/196791 http/tests/webfont/font-loading-system-fallback-visibility-FontRanges.html [ Failure ]
-webkit.org/b/196791 mathml/scripts-removeChild.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/attributes-accent-accentunder-dynamic.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/menclose-notation-attribute-add.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/menclose-notation-attribute-change-value.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/menclose-notation-attribute-remove.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/mo-form-dynamic.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/mpadded-dynamic.html [ Failure ]
-webkit.org/b/196791 mathml/presentation/stretchy-minsize-maxsize-dynamic.html [ Failure ]
-webkit.org/b/196791 svg/custom/textPath-change-id-pattern.svg [ Failure ]
-webkit.org/b/196791 svg/custom/textPath-change-id.svg [ Failure ]
-webkit.org/b/196791 svg/custom/textPath-insert-path-pattern.svg [ Failure ]
-webkit.org/b/196791 svg/custom/textPath-insert-path.svg [ Failure ]
-
 #////////////////////////////////////////////////////////////////////////////////////////
 # End of non-crashing, non-flaky tests failing
 #////////////////////////////////////////////////////////////////////////////////////////
index 73a7949..2bf34ab 100644 (file)
@@ -1,3 +1,27 @@
+2019-04-11  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] REGRESSION(r243860): Many tests failing
+        https://bugs.webkit.org/show_bug.cgi?id=196791
+
+        Reviewed by Joanmarie Diggs.
+
+        Calling updateAccessibilityTree() on document loaded was causing a re-layout because of the backing store update
+        that confused all those tests. We shouldn't need to update the accessibility tree on document load, it should
+        happen automatically when root object is attached/detached. This patch emits children-changed::add when the root
+        object wrapper is attached and children-changed::remove when the root object is detached. That way ATs are
+        notified of the changes in the accessibility tree.
+
+        * WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
+        (WebKit::WebFrameLoaderClient::dispatchDidFinishDocumentLoad): Remove call to WebPage::updateAccessibilityTree().
+        * WebProcess/WebPage/WebPage.h: Remove updateAccessibilityTree().
+        * WebProcess/WebPage/atk/WebKitWebPageAccessibilityObject.cpp:
+        (coreRootObjectWrapperDetachedCallback): Emit children-changed::remove.
+        (rootWebAreaWrapper): Helper to get the root WebArea wrapper.
+        (accessibilityRootObjectWrapper): Set the parent here when root object is created and emit children-changed::add.
+        (webkitWebPageAccessibilityObjectRefChild): Dot no set the parent here, it's now set when the root object is created.
+        * WebProcess/WebPage/atk/WebKitWebPageAccessibilityObject.h: Remove webkitWebPageAccessibilityObjectRefresh().
+        * WebProcess/WebPage/gtk/WebPageGtk.cpp:
+
 2019-04-11  Megan Gardner  <megan_gardner@apple.com>
 
         Update 'Save Image' to more clear instructions
index 3150e34..592e0a7 100644 (file)
@@ -599,11 +599,6 @@ void WebFrameLoaderClient::dispatchDidFinishDocumentLoad()
 
     // Notify the UIProcess.
     webPage->send(Messages::WebPageProxy::DidFinishDocumentLoadForFrame(m_frame->frameID(), navigationID, UserData(WebProcess::singleton().transformObjectsToHandles(userData.get()).get())));
-
-#if HAVE(ACCESSIBILITY) && PLATFORM(GTK)
-    // Ensure the accessibility hierarchy is updated.
-    webPage->updateAccessibilityTree();
-#endif
 }
 
 void WebFrameLoaderClient::dispatchDidFinishLoad()
index 8bb07e0..a34dadc 100644 (file)
@@ -805,10 +805,6 @@ public:
     void replaceSelectionWithPasteboardData(const Vector<String>& types, const IPC::DataReference&);
 #endif
 
-#if HAVE(ACCESSIBILITY) && PLATFORM(GTK)
-    void updateAccessibilityTree();
-#endif
-
     void setCompositionForTesting(const String& compositionString, uint64_t from, uint64_t length, bool suppressUnderline);
     bool hasCompositionForTesting();
     void confirmCompositionForTesting(const String& compositionString);
index 1f04c03..814947d 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "WebPage.h"
 #include <WebCore/AXObjectCache.h>
+#include <WebCore/AccessibilityScrollView.h>
 #include <WebCore/Document.h>
 #include <WebCore/Frame.h>
 #include <WebCore/Page.h>
@@ -44,6 +45,25 @@ struct _WebKitWebPageAccessibilityObjectPrivate {
 
 WEBKIT_DEFINE_TYPE(WebKitWebPageAccessibilityObject, webkit_web_page_accessibility_object, ATK_TYPE_PLUG)
 
+static void coreRootObjectWrapperDetachedCallback(AtkObject* wrapper, const char*, gboolean value, AtkObject* atkObject)
+{
+    if (!value)
+        return;
+
+    g_signal_emit_by_name(atkObject, "children-changed::remove", 0, wrapper);
+}
+
+static AccessibilityObjectWrapper* rootWebAreaWrapper(AccessibilityObject& rootObject)
+{
+    if (!rootObject.isAccessibilityScrollView())
+        return nullptr;
+
+    if (auto* webAreaObject = downcast<AccessibilityScrollView>(rootObject).webAreaObject())
+        return webAreaObject->wrapper();
+
+    return nullptr;
+}
+
 static AtkObject* accessibilityRootObjectWrapper(AtkObject* atkObject)
 {
     if (!AXObjectCache::accessibilityEnabled())
@@ -69,7 +89,21 @@ static AtkObject* accessibilityRootObjectWrapper(AtkObject* atkObject)
     if (!coreRootObject)
         return nullptr;
 
-    return ATK_OBJECT(coreRootObject->wrapper());
+    auto* wrapper = ATK_OBJECT(coreRootObject->wrapper());
+    if (!wrapper)
+        return nullptr;
+
+    if (atk_object_peek_parent(wrapper) != ATK_OBJECT(accessible)) {
+        atk_object_set_parent(wrapper, ATK_OBJECT(accessible));
+        g_signal_emit_by_name(accessible, "children-changed::add", 0, wrapper);
+
+        if (auto* webAreaWrapper = rootWebAreaWrapper(*coreRootObject)) {
+            g_signal_connect_object(webAreaWrapper, "state-change::defunct",
+                G_CALLBACK(coreRootObjectWrapperDetachedCallback), accessible, static_cast<GConnectFlags>(0));
+        }
+    }
+
+    return wrapper;
 }
 
 static void webkitWebPageAccessibilityObjectInitialize(AtkObject* atkObject, gpointer data)
@@ -98,14 +132,10 @@ static AtkObject* webkitWebPageAccessibilityObjectRefChild(AtkObject* atkObject,
     if (index && index != 1)
         return nullptr;
 
-    AtkObject* rootObject = accessibilityRootObjectWrapper(atkObject);
-    if (!rootObject)
-        return nullptr;
+    if (auto* rootObjectWrapper = accessibilityRootObjectWrapper(atkObject))
+        return ATK_OBJECT(g_object_ref(rootObjectWrapper));
 
-    atk_object_set_parent(rootObject, atkObject);
-    g_object_ref(rootObject);
-
-    return rootObject;
+    return nullptr;
 }
 
 static void webkit_web_page_accessibility_object_class_init(WebKitWebPageAccessibilityObjectClass* klass)
@@ -127,13 +157,4 @@ AtkObject* webkitWebPageAccessibilityObjectNew(WebPage* page)
     return object;
 }
 
-void webkitWebPageAccessibilityObjectRefresh(WebKitWebPageAccessibilityObject* accessible)
-{
-    // We just need to ensure that there's a connection in the ATK
-    // world between this accessibility object and the AtkObject of
-    // the accessibility object for the root of the DOM tree.
-    if (auto* rootObject = accessibilityRootObjectWrapper(ATK_OBJECT(accessible)))
-        atk_object_set_parent(rootObject, ATK_OBJECT(accessible));
-}
-
 #endif // HAVE(ACCESSIBILITY)
index 3e9f327..d6ffe03 100644 (file)
@@ -60,8 +60,6 @@ GType webkit_web_page_accessibility_object_get_type();
 
 AtkObject* webkitWebPageAccessibilityObjectNew(WebKit::WebPage*);
 
-void webkitWebPageAccessibilityObjectRefresh(WebKitWebPageAccessibilityObject*);
-
 G_END_DECLS
 
 #endif // HAVE(ACCESSIBILITY)
index 2e21ed8..261db5c 100644 (file)
@@ -111,16 +111,6 @@ void WebPage::platformEditorState(Frame& frame, EditorState& result, IncludePost
     }
 }
 
-#if HAVE(ACCESSIBILITY)
-void WebPage::updateAccessibilityTree()
-{
-    if (!m_accessibilityObject)
-        return;
-
-    webkitWebPageAccessibilityObjectRefresh(WEBKIT_WEB_PAGE_ACCESSIBILITY_OBJECT(m_accessibilityObject.get()));
-}
-#endif
-
 bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent& keyboardEvent)
 {
     if (keyboardEvent.type() != WebEvent::KeyDown && keyboardEvent.type() != WebEvent::RawKeyDown)
index bc6c671..7c7df79 100644 (file)
@@ -1,3 +1,28 @@
+2019-04-11  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        [GTK] REGRESSION(r243860): Many tests failing
+        https://bugs.webkit.org/show_bug.cgi?id=196791
+
+        Reviewed by Joanmarie Diggs.
+
+        Rework the accessibility unit test to use DBus for the communication with the server. This way we can load
+        multiple documents and check that accessibility hierarchy is updated after a navigation.
+
+        * TestWebKitAPI/Tests/WebKitGtk/AccessibilityTestServer.cpp:
+        (loadChangedCallback):
+        * TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
+        (AccessibilityTest::AccessibilityTest):
+        (AccessibilityTest::~AccessibilityTest):
+        (AccessibilityTest::loadHTMLAndWaitUntilFinished):
+        (AccessibilityTest::findTestServerApplication):
+        (AccessibilityTest::findDocumentWeb):
+        (AccessibilityTest::findRootObject):
+        (AccessibilityTest::waitUntilChildrenRemoved):
+        (AccessibilityTest::ensureProxy):
+        (testAtspiBasicHierarchy):
+        (beforeAll):
+        (afterAll):
+
 2019-04-11  Aakash Jain  <aakash_jain@apple.com>
 
         [ews-build] Handle bug titles with unicode characters
index 165fcfb..f6f7829 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 Igalia S.L.
+ * Copyright (C) 2012, 2019 Igalia S.L.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 #include <gtk/gtk.h>
 #include <webkit2/webkit2.h>
 
-static void loadChangedCallback(WebKitWebView*, WebKitLoadEvent loadEvent, gpointer)
+static const char introspectionXML[] =
+    "<node>"
+    " <interface name='org.webkit.gtk.AccessibilityTest'>"
+    "  <method name='LoadHTML'>"
+    "   <arg type='s' name='html' direction='in'/>"
+    "   <arg type='s' name='baseURI' direction='in'/>"
+    "  </method>"
+    " </interface>"
+    "</node>";
+
+static void loadChangedCallback(WebKitWebView* webView, WebKitLoadEvent loadEvent, GDBusMethodInvocation* invocation)
 {
-    // Send a message to the parent process when we're ready.
-    if (loadEvent == WEBKIT_LOAD_FINISHED)
-        g_print("OK");
+    if (loadEvent != WEBKIT_LOAD_FINISHED)
+        return;
+
+    g_signal_handlers_disconnect_by_func(webView, reinterpret_cast<void*>(loadChangedCallback), invocation);
+    g_dbus_method_invocation_return_value(invocation, nullptr);
 }
 
+static const GDBusInterfaceVTable interfaceVirtualTable = {
+    // methodCall
+    [](GDBusConnection* connection, const char* sender, const char* objectPath, const char* interfaceName, const char* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
+        if (g_strcmp0(interfaceName, "org.webkit.gtk.AccessibilityTest"))
+            return;
+
+        auto* webView = WEBKIT_WEB_VIEW(userData);
+
+        if (!g_strcmp0(methodName, "LoadHTML")) {
+            const char* html;
+            const char* baseURI;
+            g_variant_get(parameters, "(&s&s)", &html, &baseURI);
+            g_signal_connect(webView, "load-changed", G_CALLBACK(loadChangedCallback), invocation);
+            webkit_web_view_load_html(webView, html, baseURI && *baseURI ? baseURI : nullptr);
+        }
+    },
+    nullptr,
+    nullptr,
+    { 0, }
+};
+
 int main(int argc, char** argv)
 {
     // Make sure that the ATK bridge is loaded.
@@ -36,23 +69,22 @@ int main(int argc, char** argv)
 
     gtk_init(&argc, &argv);
 
-    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
-    webkit_web_view_load_html(webView,
-        "<html>"
-        "  <body>"
-        "   <h1>This is a test</h1>"
-        "   <p>This is a paragraph with some plain text.</p>"
-        "   <p>This paragraph contains <a href=\"http://www.webkitgtk.org\">a link</a> in the middle.</p>"
-        "  </body>"
-        "</html>",
-        0);
+    GtkWidget* webView = webkit_web_view_new();
 
     GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), nullptr);
     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(webView));
     gtk_widget_show_all(window);
 
-    g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), 0);
-    g_signal_connect(webView, "load-changed", G_CALLBACK(loadChangedCallback), 0);
+    g_bus_own_name(G_BUS_TYPE_SESSION, "org.webkit.gtk.AccessibilityTest", G_BUS_NAME_OWNER_FLAGS_NONE,
+        [](GDBusConnection* connection, const char* name, gpointer userData) {
+            static GDBusNodeInfo *introspectionData = nullptr;
+            if (!introspectionData)
+                introspectionData = g_dbus_node_info_new_for_xml(introspectionXML, nullptr);
+
+            g_dbus_connection_register_object(connection, "/org/webkit/gtk/AccessibilityTest", introspectionData->interfaces[0],
+                &interfaceVirtualTable, userData, nullptr, nullptr);
+        }, nullptr, nullptr, webView, nullptr);
 
     gtk_main();
 }
index 5104872..e0e00a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 Igalia S.L.
+ * Copyright (C) 2012, 2019 Igalia S.L.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 #include "config.h"
 
 #include "TestMain.h"
-#include "WebViewTest.h"
+#include "WebKitTestBus.h"
 
 // The libatspi headers don't use G_BEGIN_DECLS
 extern "C" {
 #include <atspi/atspi.h>
 }
 
-#include <errno.h>
-#include <fcntl.h>
-#include <glib.h>
-#include <signal.h>
-#include <unistd.h>
-#include <wtf/glib/GRefPtr.h>
-#include <wtf/glib/GUniquePtr.h>
+static WebKitTestBus* bus;
 
-// Name of the test server application creating the webView object.
-static const char* kTestServerAppName = "AccessibilityTestServer";
+class AccessibilityTest : public Test {
+public:
+    MAKE_GLIB_TEST_FIXTURE(AccessibilityTest);
 
-// Max seconds to wait for the test server before inspecting it.
-static const int kMaxWaitForChild = 5;
+    AccessibilityTest()
+    {
+        GUniquePtr<char> testServerPath(g_build_filename(WEBKIT_EXEC_PATH, "TestWebKitAPI", "WebKit2Gtk", "AccessibilityTestServer", nullptr));
+        char* args[2];
+        args[0] = testServerPath.get();
+        args[1] = nullptr;
 
-// The PID for the test server running, so we can kill it if needed.
-static GPid kChildProcessPid = 0;
-
-// Whether the child has replied and it's ready.
-static bool kChildIsReady = false;
+        g_assert_true(g_spawn_async(nullptr, args, nullptr, G_SPAWN_DEFAULT, nullptr, nullptr, &m_childProcessID, nullptr));
+    }
 
-static void stopTestServer()
-{
-    // Do nothing if there's no server running.
-    if (!kChildProcessPid)
-        return;
+    ~AccessibilityTest()
+    {
+        if (m_childProcessID) {
+            g_spawn_close_pid(m_childProcessID);
+            kill(m_childProcessID, SIGTERM);
+        }
+    }
 
-    g_spawn_close_pid(kChildProcessPid);
-    kill(kChildProcessPid, SIGTERM);
-    kChildProcessPid = 0;
-}
+    void loadHTMLAndWaitUntilFinished(const char* html, const char* baseURI)
+    {
+        ensureProxy();
 
-static void sigAbortHandler(int sigNum)
-{
-    // Just stop the test server if SIGABRT was received.
-    stopTestServer();
-}
-
-static gpointer testServerMonitorThreadFunc(gpointer)
-{
-    // Wait for the specified timeout to happen.
-    g_usleep(kMaxWaitForChild * G_USEC_PER_SEC);
+        GUniqueOutPtr<GError> error;
+        GRefPtr<GVariant> result = adoptGRef(g_dbus_proxy_call_sync(m_proxy.get(), "LoadHTML",
+            g_variant_new("(ss)", html, baseURI ? baseURI : ""), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error.outPtr()));
+        g_assert_no_error(error.get());
+    }
 
-    // Kill the child process if not ready yet.
-    if (!kChildIsReady)
-        stopTestServer();
+    GRefPtr<AtspiAccessible> findTestServerApplication()
+    {
+        // Only one desktop is supported by ATSPI at the moment.
+        GRefPtr<AtspiAccessible> desktop = adoptGRef(atspi_get_desktop(0));
 
-    g_thread_exit(0);
-    return 0;
-}
+        int childCount = atspi_accessible_get_child_count(desktop.get(), nullptr);
+        for (int i = 0; i < childCount; ++i) {
+            GRefPtr<AtspiAccessible> current = adoptGRef(atspi_accessible_get_child_at_index(desktop.get(), i, nullptr));
+            if (!g_strcmp0(atspi_accessible_get_name(current.get(), nullptr), "AccessibilityTestServer"))
+                return current;
+        }
 
-static void startTestServerMonitor()
-{
-    kChildIsReady = false;
-    g_thread_new("TestServerMonitor", testServerMonitorThreadFunc, 0);
-}
+        return 0;
+    }
 
-static void startTestServer()
-{
-    // Prepare argv[] for spawning the server process.
-    GUniquePtr<char> testServerPath(g_build_filename(WEBKIT_EXEC_PATH, "TestWebKitAPI", "WebKit2Gtk", kTestServerAppName, nullptr));
-
-    char* testServerArgv[2];
-    testServerArgv[0] = testServerPath.get();
-    testServerArgv[1] = 0;
-
-    // Spawn the server, getting its stdout file descriptor to set a
-    // communication channel, so we know when it's ready.
-    int childStdout = 0;
-    if (!g_spawn_async_with_pipes(0, testServerArgv, 0, static_cast<GSpawnFlags>(0), 0, 0, &kChildProcessPid, 0, &childStdout, 0, 0)) {
-        close(childStdout);
-        return;
+    GRefPtr<AtspiAccessible> findDocumentWeb(AtspiAccessible* accessible)
+    {
+        int childCount = atspi_accessible_get_child_count(accessible, nullptr);
+        for (int i = 0; i < childCount; ++i) {
+            GRefPtr<AtspiAccessible> child = adoptGRef(atspi_accessible_get_child_at_index(accessible, i, nullptr));
+            if (atspi_accessible_get_role(child.get(), nullptr) == ATSPI_ROLE_DOCUMENT_WEB)
+                return child;
+
+            if (auto documentWeb = findDocumentWeb(child.get()))
+                return documentWeb;
+        }
+        return nullptr;
     }
 
-    // Start monitoring the test server (in a separate thread) to
-    // ensure we don't block on the child process more than a timeout.
-    startTestServerMonitor();
+    GRefPtr<AtspiAccessible> findRootObject(AtspiAccessible* application)
+    {
+        // Find the document web, its parent is the scroll view (WebCore root object) and its parent is
+        // the GtkPlug (WebProcess root element).
+        auto documentWeb = findDocumentWeb(application);
+        if (!documentWeb)
+            return nullptr;
 
-    char msg[2];
-    GIOChannel* ioChannel = g_io_channel_unix_new(childStdout);
-    if (g_io_channel_read_chars(ioChannel, msg, 2, 0, 0) == G_IO_STATUS_NORMAL) {
-        // Check whether the server sent a message saying it's ready
-        // and store the result globally, so the monitor can see it.
-        kChildIsReady = msg[0] == 'O' && msg[1] == 'K';
+        auto parent = adoptGRef(atspi_accessible_get_parent(documentWeb.get(), nullptr));
+        return parent ? adoptGRef(atspi_accessible_get_parent(parent.get(), nullptr)) : nullptr;
     }
-    g_io_channel_unref(ioChannel);
-    close(childStdout);
-
-    // The timeout was reached and the server is not ready yet, so
-    // stop it inmediately, and let the unit tests fail.
-    if (!kChildIsReady)
-        stopTestServer();
-}
 
-static void checkAtspiAccessible(AtspiAccessible* accessible, const char* targetName, AtspiRole targetRole)
-{
-    g_assert_true(ATSPI_IS_ACCESSIBLE(accessible));
+    void waitUntilChildrenRemoved(AtspiAccessible* accessible)
+    {
+        m_eventSource = accessible;
+        GRefPtr<AtspiEventListener> listener = adoptGRef(atspi_event_listener_new(
+            [](AtspiEvent* event, gpointer userData) {
+                auto* test = static_cast<AccessibilityTest*>(userData);
+                if (event->source == test->m_eventSource)
+                    g_main_loop_quit(test->m_mainLoop.get());
+        }, this, nullptr));
+        atspi_event_listener_register(listener.get(), "object:children-changed:remove", nullptr);
+        g_main_loop_run(m_mainLoop.get());
+        m_eventSource = nullptr;
+    }
 
-    GUniquePtr<char> name(atspi_accessible_get_name(accessible, 0));
-    g_assert_cmpstr(targetName, ==, name.get());
-    g_assert_cmpint(targetRole, ==, atspi_accessible_get_role(accessible, 0));
-}
+private:
+    void ensureProxy()
+    {
+        if (m_proxy)
+            return;
 
-static GRefPtr<AtspiAccessible> findTestServerApplication()
-{
-    // Only one desktop is supported by ATSPI at the moment.
-    GRefPtr<AtspiAccessible> desktop = adoptGRef(atspi_get_desktop(0));
-
-    // Look for the server application in the list of apps.
-    GRefPtr<AtspiAccessible> current;
-    int childCount = atspi_accessible_get_child_count(desktop.get(), 0);
-    for (int i = 0; i < childCount; i++) {
-        current = adoptGRef(atspi_accessible_get_child_at_index(desktop.get(), i, 0));
-        if (!g_strcmp0(atspi_accessible_get_name(current.get(), 0), kTestServerAppName))
-            return current;
+        m_mainLoop = adoptGRef(g_main_loop_new(nullptr, FALSE));
+        m_proxy = adoptGRef(bus->createProxy("org.webkit.gtk.AccessibilityTest", "/org/webkit/gtk/AccessibilityTest", "org.webkit.gtk.AccessibilityTest", m_mainLoop.get()));
     }
 
-    return 0;
-}
+    GPid m_childProcessID { 0 };
+    GRefPtr<GDBusProxy> m_proxy;
+    GRefPtr<GMainLoop> m_mainLoop;
+    AtspiAccessible* m_eventSource { nullptr };
+};
 
-static void testAtspiBasicHierarchy(WebViewTest* test, gconstpointer)
+static void testAtspiBasicHierarchy(AccessibilityTest* test, gconstpointer)
 {
-    // The test server's accessibility object (UI Process).
-    GRefPtr<AtspiAccessible> testServerApp = findTestServerApplication();
+    test->loadHTMLAndWaitUntilFinished(
+        "<html>"
+        "  <body>"
+        "   <h1>This is a test</h1>"
+        "   <p>This is a paragraph with some plain text.</p>"
+        "   <p>This paragraph contains <a href=\"http://www.webkitgtk.org\">a link</a> in the middle.</p>"
+        "  </body>"
+        "</html>",
+        nullptr);
+
+    auto testServerApp = test->findTestServerApplication();
     g_assert_true(ATSPI_IS_ACCESSIBLE(testServerApp.get()));
-    checkAtspiAccessible(testServerApp.get(), "AccessibilityTestServer", ATSPI_ROLE_APPLICATION);
-
-    // The main window's accessibility object (UI Process).
-    GRefPtr<AtspiAccessible> currentParent = testServerApp;
-    GRefPtr<AtspiAccessible> currentChild = adoptGRef(atspi_accessible_get_child_at_index(currentParent.get(), 0, 0));
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "", ATSPI_ROLE_FRAME);
-
-    // The WebView's accessibility object (UI Process).
-    currentParent = currentChild;
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 0, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "", ATSPI_ROLE_FILLER);
-
-    // The WebPage's accessibility object (Web Process).
-    currentParent = currentChild;
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 0, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "", ATSPI_ROLE_FILLER);
-
-    // HTML root element's accessible element (Web Process).
-    currentParent = currentChild;
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 0, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-
-    // HTML body's accessible element (Web Process).
-    currentParent = currentChild;
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 0, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "", ATSPI_ROLE_DOCUMENT_WEB);
-
-    // HTML H1's accessible element (Web Process).
-    currentParent = currentChild;
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 0, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "This is a test", ATSPI_ROLE_HEADING);
-
-    // HTML first paragraph's accessible element (Web Process).
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 1, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "", ATSPI_ROLE_PARAGRAPH);
-
-    // HTML second paragraph's accessible element (Web Process).
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 2, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "", ATSPI_ROLE_PARAGRAPH);
-
-    // HTML link's accessible element (Web Process).
-    currentParent = currentChild;
-    currentChild = atspi_accessible_get_child_at_index(currentParent.get(), 0, 0);
-    g_assert_true(ATSPI_IS_ACCESSIBLE(currentChild.get()));
-    checkAtspiAccessible(currentChild.get(), "a link", ATSPI_ROLE_LINK);
+    GUniquePtr<char> name(atspi_accessible_get_name(testServerApp.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "AccessibilityTestServer");
+    g_assert_cmpint(atspi_accessible_get_role(testServerApp.get(), nullptr), ==, ATSPI_ROLE_APPLICATION);
+
+    auto rootObject = test->findRootObject(testServerApp.get());
+    g_assert_true(ATSPI_IS_ACCESSIBLE(rootObject.get()));
+    g_assert_cmpint(atspi_accessible_get_role(rootObject.get(), nullptr), ==, ATSPI_ROLE_FILLER);
+
+    auto scrollView = adoptGRef(atspi_accessible_get_child_at_index(rootObject.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(scrollView.get()));
+    g_assert_cmpint(atspi_accessible_get_role(scrollView.get(), nullptr), ==, ATSPI_ROLE_SCROLL_PANE);
+
+    auto documentWeb = adoptGRef(atspi_accessible_get_child_at_index(scrollView.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+    g_assert_cmpint(atspi_accessible_get_role(documentWeb.get(), nullptr), ==, ATSPI_ROLE_DOCUMENT_WEB);
+
+    auto h1 = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(h1.get()));
+    name.reset(atspi_accessible_get_name(h1.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "This is a test");
+    g_assert_cmpint(atspi_accessible_get_role(h1.get(), nullptr), ==, ATSPI_ROLE_HEADING);
+
+    auto p1 = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(p1.get()));
+    g_assert_cmpint(atspi_accessible_get_role(p1.get(), nullptr), ==, ATSPI_ROLE_PARAGRAPH);
+
+    auto p2 = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 2, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(p2.get()));
+    g_assert_cmpint(atspi_accessible_get_role(p2.get(), nullptr), ==, ATSPI_ROLE_PARAGRAPH);
+
+    auto link = adoptGRef(atspi_accessible_get_child_at_index(p2.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(link.get()));
+    name.reset(atspi_accessible_get_name(link.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "a link");
+    g_assert_cmpint(atspi_accessible_get_role(link.get(), nullptr), ==, ATSPI_ROLE_LINK);
+
+    test->loadHTMLAndWaitUntilFinished(
+        "<html>"
+        "  <body>"
+        "   <h1>This is another test</h1>"
+        "   <img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AYWDTMVwnSZnwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFklEQVQI12P8z8DAwMDAxMDAwMDAAAANHQEDK+mmyAAAAABJRU5ErkJggg=='/>"
+        "  </body>"
+        "</html>",
+        nullptr);
+
+    // Check that children-changed::remove is emitted on the root object on navigation,
+    // and the a11y hierarchy is updated.
+    test->waitUntilChildrenRemoved(rootObject.get());
+
+    documentWeb = test->findDocumentWeb(testServerApp.get());
+    g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+    g_assert_cmpint(atspi_accessible_get_role(documentWeb.get(), nullptr), ==, ATSPI_ROLE_DOCUMENT_WEB);
+
+    h1 = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(h1.get()));
+    name.reset(atspi_accessible_get_name(h1.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "This is another test");
+    g_assert_cmpint(atspi_accessible_get_role(h1.get(), nullptr), ==, ATSPI_ROLE_HEADING);
+
+    auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
+    g_assert_cmpint(atspi_accessible_get_role(section.get(), nullptr), ==, ATSPI_ROLE_SECTION);
+
+    auto img = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(img.get()));
+    g_assert_cmpint(atspi_accessible_get_role(img.get(), nullptr), ==, ATSPI_ROLE_IMAGE);
 }
 
 void beforeAll()
 {
-    // We install a handler to ensure that we kill the child process
-    // if the parent dies because of whatever the reason is.
-    signal(SIGABRT, sigAbortHandler);
+    bus = new WebKitTestBus();
+    if (!bus->run())
+        return;
 
-    // Start the accessibility test server and load the tests.
-    startTestServer();
-    WebViewTest::add("WebKitAccessibility", "atspi-basic-hierarchy", testAtspiBasicHierarchy);
+    AccessibilityTest::add("WebKitAccessibility", "atspi-basic-hierarchy", testAtspiBasicHierarchy);
 }
 
 void afterAll()
 {
-    // Ensure we stop the server.
-    stopTestServer();
+    delete bus;
 }