| 1 | /* |
| 2 | * Copyright (C) 2012, 2019 Igalia S.L. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "WebKitWebPageAccessibilityObject.h" |
| 28 | |
| 29 | #if HAVE(ACCESSIBILITY) |
| 30 | |
| 31 | #include "WebPage.h" |
| 32 | #include <WebCore/AXObjectCache.h> |
| 33 | #include <WebCore/AccessibilityScrollView.h> |
| 34 | #include <WebCore/Document.h> |
| 35 | #include <WebCore/Frame.h> |
| 36 | #include <WebCore/Page.h> |
| 37 | #include <wtf/glib/WTFGType.h> |
| 38 | |
| 39 | using namespace WebKit; |
| 40 | using namespace WebCore; |
| 41 | |
| 42 | struct _WebKitWebPageAccessibilityObjectPrivate { |
| 43 | WebPage* page; |
| 44 | }; |
| 45 | |
| 46 | WEBKIT_DEFINE_TYPE(WebKitWebPageAccessibilityObject, webkit_web_page_accessibility_object, ATK_TYPE_PLUG) |
| 47 | |
| 48 | static void coreRootObjectWrapperDetachedCallback(AtkObject* wrapper, const char*, gboolean value, AtkObject* atkObject) |
| 49 | { |
| 50 | if (!value) |
| 51 | return; |
| 52 | |
| 53 | g_signal_emit_by_name(atkObject, "children-changed::remove" , 0, wrapper); |
| 54 | } |
| 55 | |
| 56 | static AccessibilityObjectWrapper* rootWebAreaWrapper(AccessibilityObject& rootObject) |
| 57 | { |
| 58 | if (!rootObject.isAccessibilityScrollView()) |
| 59 | return nullptr; |
| 60 | |
| 61 | if (auto* webAreaObject = downcast<AccessibilityScrollView>(rootObject).webAreaObject()) |
| 62 | return webAreaObject->wrapper(); |
| 63 | |
| 64 | return nullptr; |
| 65 | } |
| 66 | |
| 67 | static AtkObject* accessibilityRootObjectWrapper(AtkObject* atkObject) |
| 68 | { |
| 69 | if (!AXObjectCache::accessibilityEnabled()) |
| 70 | AXObjectCache::enableAccessibility(); |
| 71 | |
| 72 | auto* accessible = WEBKIT_WEB_PAGE_ACCESSIBILITY_OBJECT(atkObject); |
| 73 | if (!accessible->priv->page) |
| 74 | return nullptr; |
| 75 | |
| 76 | Page* corePage = accessible->priv->page->corePage(); |
| 77 | if (!corePage) |
| 78 | return nullptr; |
| 79 | |
| 80 | Frame& coreFrame = corePage->mainFrame(); |
| 81 | if (!coreFrame.document()) |
| 82 | return nullptr; |
| 83 | |
| 84 | AXObjectCache* cache = coreFrame.document()->axObjectCache(); |
| 85 | if (!cache) |
| 86 | return nullptr; |
| 87 | |
| 88 | AccessibilityObject* coreRootObject = cache->rootObject(); |
| 89 | if (!coreRootObject) |
| 90 | return nullptr; |
| 91 | |
| 92 | auto* wrapper = ATK_OBJECT(coreRootObject->wrapper()); |
| 93 | if (!wrapper) |
| 94 | return nullptr; |
| 95 | |
| 96 | if (atk_object_peek_parent(wrapper) != ATK_OBJECT(accessible)) { |
| 97 | atk_object_set_parent(wrapper, ATK_OBJECT(accessible)); |
| 98 | g_signal_emit_by_name(accessible, "children-changed::add" , 0, wrapper); |
| 99 | |
| 100 | if (auto* webAreaWrapper = rootWebAreaWrapper(*coreRootObject)) { |
| 101 | g_signal_connect_object(webAreaWrapper, "state-change::defunct" , |
| 102 | G_CALLBACK(coreRootObjectWrapperDetachedCallback), accessible, static_cast<GConnectFlags>(0)); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | return wrapper; |
| 107 | } |
| 108 | |
| 109 | static void webkitWebPageAccessibilityObjectInitialize(AtkObject* atkObject, gpointer data) |
| 110 | { |
| 111 | if (ATK_OBJECT_CLASS(webkit_web_page_accessibility_object_parent_class)->initialize) |
| 112 | ATK_OBJECT_CLASS(webkit_web_page_accessibility_object_parent_class)->initialize(atkObject, data); |
| 113 | |
| 114 | WEBKIT_WEB_PAGE_ACCESSIBILITY_OBJECT(atkObject)->priv->page = reinterpret_cast<WebPage*>(data); |
| 115 | atk_object_set_role(atkObject, ATK_ROLE_FILLER); |
| 116 | } |
| 117 | |
| 118 | static gint webkitWebPageAccessibilityObjectGetIndexInParent(AtkObject*) |
| 119 | { |
| 120 | // An AtkPlug is the only child an AtkSocket can have. |
| 121 | return 0; |
| 122 | } |
| 123 | |
| 124 | static gint webkitWebPageAccessibilityObjectGetNChildren(AtkObject* atkObject) |
| 125 | { |
| 126 | return accessibilityRootObjectWrapper(atkObject) ? 1 : 0; |
| 127 | } |
| 128 | |
| 129 | static AtkObject* webkitWebPageAccessibilityObjectRefChild(AtkObject* atkObject, gint index) |
| 130 | { |
| 131 | // It's supposed to have either one child or zero. |
| 132 | if (index && index != 1) |
| 133 | return nullptr; |
| 134 | |
| 135 | if (auto* rootObjectWrapper = accessibilityRootObjectWrapper(atkObject)) |
| 136 | return ATK_OBJECT(g_object_ref(rootObjectWrapper)); |
| 137 | |
| 138 | return nullptr; |
| 139 | } |
| 140 | |
| 141 | static void webkit_web_page_accessibility_object_class_init(WebKitWebPageAccessibilityObjectClass* klass) |
| 142 | { |
| 143 | AtkObjectClass* atkObjectClass = ATK_OBJECT_CLASS(klass); |
| 144 | // No need to implement get_parent() here since this is a subclass |
| 145 | // of AtkPlug and all the logic related to that function will be |
| 146 | // implemented by the ATK bridge. |
| 147 | atkObjectClass->initialize = webkitWebPageAccessibilityObjectInitialize; |
| 148 | atkObjectClass->get_index_in_parent = webkitWebPageAccessibilityObjectGetIndexInParent; |
| 149 | atkObjectClass->get_n_children = webkitWebPageAccessibilityObjectGetNChildren; |
| 150 | atkObjectClass->ref_child = webkitWebPageAccessibilityObjectRefChild; |
| 151 | } |
| 152 | |
| 153 | AtkObject* webkitWebPageAccessibilityObjectNew(WebPage* page) |
| 154 | { |
| 155 | AtkObject* object = ATK_OBJECT(g_object_new(WEBKIT_TYPE_WEB_PAGE_ACCESSIBILITY_OBJECT, nullptr)); |
| 156 | atk_object_initialize(object, page); |
| 157 | return object; |
| 158 | } |
| 159 | |
| 160 | #endif // HAVE(ACCESSIBILITY) |
| 161 | |