1/*
2 * Copyright (C) 2010, 2011, 2012 Igalia S.L.
3 * Copyright (C) 2013 Samsung Electronics
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "WebKitAccessibleHyperlink.h"
23
24#if HAVE(ACCESSIBILITY)
25
26#include "AXObjectCache.h"
27#include "AccessibilityObject.h"
28#include "Editing.h"
29#include "NotImplemented.h"
30#include "Position.h"
31#include "Range.h"
32#include "RenderListMarker.h"
33#include "RenderObject.h"
34#include "TextIterator.h"
35#include "WebKitAccessible.h"
36#include "WebKitAccessibleUtil.h"
37#include <wtf/glib/WTFGType.h>
38#include <wtf/text/CString.h>
39
40using namespace WebCore;
41
42static void webkit_accessible_hyperlink_atk_action_interface_init(AtkActionIface*);
43
44struct _WebKitAccessibleHyperlinkPrivate {
45 WebKitAccessible* hyperlinkImpl;
46
47 // We cache these values so we can return them as const values.
48 CString actionName;
49 CString actionKeyBinding;
50};
51
52WEBKIT_DEFINE_TYPE_WITH_CODE(
53 WebKitAccessibleHyperlink, webkit_accessible_hyperlink, ATK_TYPE_HYPERLINK,
54 G_IMPLEMENT_INTERFACE(ATK_TYPE_ACTION, webkit_accessible_hyperlink_atk_action_interface_init))
55
56enum {
57 PROP_0,
58
59 PROP_HYPERLINK_IMPL
60};
61
62static gboolean webkitAccessibleHyperlinkActionDoAction(AtkAction* action, gint)
63{
64 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(action);
65 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, FALSE);
66
67 if (!ATK_IS_ACTION(accessibleHyperlink->priv->hyperlinkImpl))
68 return FALSE;
69
70 auto& coreObject = webkitAccessibleGetAccessibilityObject(accessibleHyperlink->priv->hyperlinkImpl);
71 return coreObject.performDefaultAction();
72}
73
74static gint webkitAccessibleHyperlinkActionGetNActions(AtkAction* action)
75{
76 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(action);
77 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, 0);
78
79 return ATK_IS_ACTION(accessibleHyperlink->priv->hyperlinkImpl) ? 1 : 0;
80}
81
82static const gchar* webkitAccessibleHyperlinkActionGetDescription(AtkAction* action, gint)
83{
84 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(action);
85 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, nullptr);
86
87 // TODO: Need a way to provide/localize action descriptions.
88 notImplemented();
89 return "";
90}
91
92static const gchar* webkitAccessibleHyperlinkActionGetKeybinding(AtkAction* action, gint)
93{
94 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(action);
95 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, nullptr);
96
97 if (!ATK_IS_ACTION(accessibleHyperlink->priv->hyperlinkImpl))
98 return nullptr;
99
100 auto& coreObject = webkitAccessibleGetAccessibilityObject(accessibleHyperlink->priv->hyperlinkImpl);
101 accessibleHyperlink->priv->actionKeyBinding = coreObject.accessKey().string().utf8();
102 return accessibleHyperlink->priv->actionKeyBinding.data();
103}
104
105static const gchar* webkitAccessibleHyperlinkActionGetName(AtkAction* action, gint)
106{
107 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(action);
108 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, nullptr);
109
110 if (!ATK_IS_ACTION(accessibleHyperlink->priv->hyperlinkImpl))
111 return nullptr;
112
113 auto& coreObject = webkitAccessibleGetAccessibilityObject(accessibleHyperlink->priv->hyperlinkImpl);
114 accessibleHyperlink->priv->actionName = coreObject.actionVerb().utf8();
115 return accessibleHyperlink->priv->actionName.data();
116}
117
118static void webkit_accessible_hyperlink_atk_action_interface_init(AtkActionIface* iface)
119{
120 iface->do_action = webkitAccessibleHyperlinkActionDoAction;
121 iface->get_n_actions = webkitAccessibleHyperlinkActionGetNActions;
122 iface->get_description = webkitAccessibleHyperlinkActionGetDescription;
123 iface->get_keybinding = webkitAccessibleHyperlinkActionGetKeybinding;
124 iface->get_name = webkitAccessibleHyperlinkActionGetName;
125}
126
127static gchar* webkitAccessibleHyperlinkGetURI(AtkHyperlink* link, gint index)
128{
129 // FIXME: Do NOT support more than one instance of an AtkObject
130 // implementing AtkHyperlinkImpl in every instance of AtkHyperLink
131 if (index)
132 return nullptr;
133
134 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
135 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, nullptr);
136
137 auto& coreObject = webkitAccessibleGetAccessibilityObject(accessibleHyperlink->priv->hyperlinkImpl);
138 return !coreObject.url().isNull() ? g_strdup(coreObject.url().string().utf8().data()) : nullptr;
139}
140
141static AtkObject* webkitAccessibleHyperlinkGetObject(AtkHyperlink* link, gint index)
142{
143 // FIXME: Do NOT support more than one instance of an AtkObject
144 // implementing AtkHyperlinkImpl in every instance of AtkHyperLink
145 if (index)
146 return nullptr;
147
148 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
149 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, 0);
150
151 return ATK_OBJECT(accessibleHyperlink->priv->hyperlinkImpl);
152}
153
154static gint rangeLengthForObject(AccessibilityObject& obj, Range* range)
155{
156 // This is going to be the actual length in most of the cases
157 int baseLength = TextIterator::rangeLength(range, true);
158
159 // Check whether the current hyperlink belongs to a list item.
160 // If so, we need to consider the length of the item's marker
161 AccessibilityObject* parent = obj.parentObjectUnignored();
162 if (!parent || !parent->isAccessibilityRenderObject() || !parent->isListItem())
163 return baseLength;
164
165 // Even if we don't expose list markers to Assistive
166 // Technologies, we need to have a way to measure their length
167 // for those cases when it's needed to take it into account
168 // separately (as in getAccessibilityObjectForOffset)
169 AccessibilityObject* markerObj = parent->firstChild();
170 if (!markerObj)
171 return baseLength;
172
173 RenderObject* renderer = markerObj->renderer();
174 if (!is<RenderListMarker>(renderer))
175 return baseLength;
176
177 auto& marker = downcast<RenderListMarker>(*renderer);
178 return baseLength + marker.text().length() + marker.suffix().length();
179}
180
181static gint webkitAccessibleHyperlinkGetStartIndex(AtkHyperlink* link)
182{
183 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
184 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, 0);
185
186 auto& coreObject = webkitAccessibleGetAccessibilityObject(accessibleHyperlink->priv->hyperlinkImpl);
187 AccessibilityObject* parentUnignored = coreObject.parentObjectUnignored();
188 if (!parentUnignored)
189 return 0;
190
191 Node* node = coreObject.node();
192 if (!node)
193 return 0;
194
195 Node* parentNode = parentUnignored->node();
196 if (!parentNode)
197 return 0;
198
199 auto range = Range::create(node->document(), firstPositionInOrBeforeNode(parentNode), firstPositionInOrBeforeNode(node));
200 return rangeLengthForObject(coreObject, range.ptr());
201}
202
203static gint webkitAccessibleHyperlinkGetEndIndex(AtkHyperlink* link)
204{
205 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
206 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, 0);
207
208 auto& coreObject = webkitAccessibleGetAccessibilityObject(accessibleHyperlink->priv->hyperlinkImpl);
209 AccessibilityObject* parentUnignored = coreObject.parentObjectUnignored();
210 if (!parentUnignored)
211 return 0;
212
213 Node* node = coreObject.node();
214 if (!node)
215 return 0;
216
217 Node* parentNode = parentUnignored->node();
218 if (!parentNode)
219 return 0;
220
221 auto range = Range::create(node->document(), firstPositionInOrBeforeNode(parentNode), lastPositionInOrAfterNode(node));
222 return rangeLengthForObject(coreObject, range.ptr());
223}
224
225static gboolean webkitAccessibleHyperlinkIsValid(AtkHyperlink* link)
226{
227 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
228 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, FALSE);
229
230 // Link is valid for the whole object's lifetime
231 return TRUE;
232}
233
234static gint webkitAccessibleHyperlinkGetNAnchors(AtkHyperlink* link)
235{
236 // FIXME Do NOT support more than one instance of an AtkObject
237 // implementing AtkHyperlinkImpl in every instance of AtkHyperLink
238 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
239 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, 0);
240
241 return 1;
242}
243
244static gboolean webkitAccessibleHyperlinkIsSelectedLink(AtkHyperlink* link)
245{
246 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(link);
247 returnValIfWebKitAccessibleIsInvalid(accessibleHyperlink->priv->hyperlinkImpl, FALSE);
248
249 // Not implemented: this function is deprecated in ATK now
250 notImplemented();
251 return FALSE;
252}
253
254static void webkitAccessibleHyperlinkGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* pspec)
255{
256 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(object);
257
258 switch (propId) {
259 case PROP_HYPERLINK_IMPL:
260 g_value_set_object(value, accessibleHyperlink->priv->hyperlinkImpl);
261 break;
262 default:
263 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
264 }
265}
266
267static void webkitAccessibleHyperlinkSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* pspec)
268{
269 auto* accessibleHyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(object);
270
271 switch (propId) {
272 case PROP_HYPERLINK_IMPL:
273 accessibleHyperlink->priv->hyperlinkImpl = WEBKIT_ACCESSIBLE(g_value_get_object(value));
274 break;
275 default:
276 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
277 }
278}
279
280static void webkit_accessible_hyperlink_class_init(WebKitAccessibleHyperlinkClass* klass)
281{
282 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
283 gobjectClass->set_property = webkitAccessibleHyperlinkSetProperty;
284 gobjectClass->get_property = webkitAccessibleHyperlinkGetProperty;
285
286 AtkHyperlinkClass* atkHyperlinkClass = ATK_HYPERLINK_CLASS(klass);
287 atkHyperlinkClass->get_uri = webkitAccessibleHyperlinkGetURI;
288 atkHyperlinkClass->get_object = webkitAccessibleHyperlinkGetObject;
289 atkHyperlinkClass->get_start_index = webkitAccessibleHyperlinkGetStartIndex;
290 atkHyperlinkClass->get_end_index = webkitAccessibleHyperlinkGetEndIndex;
291 atkHyperlinkClass->is_valid = webkitAccessibleHyperlinkIsValid;
292 atkHyperlinkClass->get_n_anchors = webkitAccessibleHyperlinkGetNAnchors;
293 atkHyperlinkClass->is_selected_link = webkitAccessibleHyperlinkIsSelectedLink;
294
295 g_object_class_install_property(gobjectClass, PROP_HYPERLINK_IMPL,
296 g_param_spec_object("hyperlink-impl",
297 "Hyperlink implementation",
298 "The associated WebKitAccessible instance.",
299 WEBKIT_TYPE_ACCESSIBLE,
300 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
301}
302
303WebKitAccessibleHyperlink* webkitAccessibleHyperlinkGetOrCreate(AtkHyperlinkImpl* hyperlinkImpl)
304{
305 g_return_val_if_fail(ATK_IS_HYPERLINK_IMPL(hyperlinkImpl), nullptr);
306 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(hyperlinkImpl), nullptr);
307
308 if (auto* currentHyperLink = g_object_get_data(G_OBJECT(hyperlinkImpl), "webkit-accessible-hyperlink-object"))
309 return WEBKIT_ACCESSIBLE_HYPERLINK(g_object_ref(currentHyperLink));
310
311 auto* hyperlink = WEBKIT_ACCESSIBLE_HYPERLINK(g_object_new(WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, "hyperlink-impl", hyperlinkImpl, nullptr));
312 g_object_set_data_full(G_OBJECT(hyperlinkImpl), "webkit-accessible-hyperlink-object", hyperlink, g_object_unref);
313 return hyperlink;
314}
315
316#endif // HAVE(ACCESSIBILITY)
317