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 "NotImplemented.h" 29#include "Position.h" 30#include "Range.h" 31#include "RenderListMarker.h" 32#include "RenderObject.h" 33#include "TextIterator.h" 34#include "WebKitAccessibleUtil.h" 35#include "WebKitAccessibleWrapperAtk.h" 36#include "htmlediting.h" 37#include <wtf/text/CString.h> 38 39#include <atk/atk.h> 40#include <glib.h> 41 42using namespace WebCore; 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 52#define WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkPrivate)) 53 54enum { 55 PROP_0, 56 57 PROP_HYPERLINK_IMPL 58}; 59 60static gpointer webkitAccessibleHyperlinkParentClass = 0; 61 62static AccessibilityObject* core(WebKitAccessible* accessible) 63{ 64 if (!accessible || !WEBKIT_IS_ACCESSIBLE(accessible)) 65 return 0; 66 67 return webkitAccessibleGetAccessibilityObject(accessible); 68} 69 70static AccessibilityObject* core(WebKitAccessibleHyperlink* link) 71{ 72 if (!link) 73 return 0; 74 75 return core(link->priv->hyperlinkImpl); 76} 77 78static AccessibilityObject* core(AtkHyperlink* link) 79{ 80 if (!WEBKIT_IS_ACCESSIBLE_HYPERLINK(link)) 81 return 0; 82 83 return core(WEBKIT_ACCESSIBLE_HYPERLINK(link)); 84} 85 86static AccessibilityObject* core(AtkAction* action) 87{ 88 return core(WEBKIT_ACCESSIBLE_HYPERLINK(action)); 89} 90 91 92static gboolean webkitAccessibleHyperlinkActionDoAction(AtkAction* action, gint index) 93{ 94 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), FALSE); 95 g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, FALSE); 96 g_return_val_if_fail(!index, FALSE); 97 98 if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) 99 return FALSE; 100 101 AccessibilityObject* coreObject = core(action); 102 if (!coreObject) 103 return FALSE; 104 105 return coreObject->performDefaultAction(); 106} 107 108static gint webkitAccessibleHyperlinkActionGetNActions(AtkAction* action) 109{ 110 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); 111 g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); 112 113 if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) 114 return 0; 115 116 return 1; 117} 118 119static const gchar* webkitAccessibleHyperlinkActionGetDescription(AtkAction* action, gint index) 120{ 121 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); 122 g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); 123 g_return_val_if_fail(!index, 0); 124 125 // TODO: Need a way to provide/localize action descriptions. 126 notImplemented(); 127 return ""; 128} 129 130static const gchar* webkitAccessibleHyperlinkActionGetKeybinding(AtkAction* action, gint index) 131{ 132 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); 133 g_return_val_if_fail(!index, 0); 134 135 WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv; 136 g_return_val_if_fail(priv->hyperlinkImpl, 0); 137 138 if (!ATK_IS_ACTION(priv->hyperlinkImpl)) 139 return 0; 140 141 AccessibilityObject* coreObject = core(action); 142 if (!coreObject) 143 return 0; 144 145 priv->actionKeyBinding = coreObject->accessKey().string().utf8(); 146 return priv->actionKeyBinding.data(); 147} 148 149static const gchar* webkitAccessibleHyperlinkActionGetName(AtkAction* action, gint index) 150{ 151 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); 152 g_return_val_if_fail(!index, 0); 153 154 WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv; 155 g_return_val_if_fail(priv->hyperlinkImpl, 0); 156 157 if (!ATK_IS_ACTION(priv->hyperlinkImpl)) 158 return 0; 159 160 AccessibilityObject* coreObject = core(action); 161 if (!coreObject) 162 return 0; 163 164 priv->actionName = coreObject->actionVerb().utf8(); 165 return priv->actionName.data(); 166} 167 168static void atkActionInterfaceInit(AtkActionIface* iface) 169{ 170 iface->do_action = webkitAccessibleHyperlinkActionDoAction; 171 iface->get_n_actions = webkitAccessibleHyperlinkActionGetNActions; 172 iface->get_description = webkitAccessibleHyperlinkActionGetDescription; 173 iface->get_keybinding = webkitAccessibleHyperlinkActionGetKeybinding; 174 iface->get_name = webkitAccessibleHyperlinkActionGetName; 175} 176 177static gchar* webkitAccessibleHyperlinkGetURI(AtkHyperlink* link, gint index) 178{ 179 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 180 // FIXME: Do NOT support more than one instance of an AtkObject 181 // implementing AtkHyperlinkImpl in every instance of AtkHyperLink 182 g_return_val_if_fail(!index, 0); 183 184 AccessibilityObject* coreObject = core(link); 185 if (!coreObject || coreObject->url().isNull()) 186 return 0; 187 188 return g_strdup(coreObject->url().string().utf8().data()); 189} 190 191static AtkObject* webkitAccessibleHyperlinkGetObject(AtkHyperlink* link, gint index) 192{ 193 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 194 g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); 195 196 // FIXME: Do NOT support more than one instance of an AtkObject 197 // implementing AtkHyperlinkImpl in every instance of AtkHyperLink 198 g_return_val_if_fail(!index, 0); 199 200 return ATK_OBJECT(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl); 201} 202 203static gint getRangeLengthForObject(AccessibilityObject* obj, Range* range) 204{ 205 // This is going to be the actual length in most of the cases 206 int baseLength = TextIterator::rangeLength(range, true); 207 208 // Check whether the current hyperlink belongs to a list item. 209 // If so, we need to consider the length of the item's marker 210 AccessibilityObject* parent = obj->parentObjectUnignored(); 211 if (!parent || !parent->isAccessibilityRenderObject() || !parent->isListItem()) 212 return baseLength; 213 214 // Even if we don't expose list markers to Assistive 215 // Technologies, we need to have a way to measure their length 216 // for those cases when it's needed to take it into account 217 // separately (as in getAccessibilityObjectForOffset) 218 AccessibilityObject* markerObj = parent->firstChild(); 219 if (!markerObj) 220 return baseLength; 221 222 RenderObject* renderer = markerObj->renderer(); 223 if (!renderer || !renderer->isListMarker()) 224 return baseLength; 225 226 RenderListMarker* marker = toRenderListMarker(renderer); 227 return baseLength + marker->text().length() + marker->suffix().length(); 228} 229 230static gint webkitAccessibleHyperlinkGetStartIndex(AtkHyperlink* link) 231{ 232 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 233 234 AccessibilityObject* coreObject = core(link); 235 if (!coreObject) 236 return 0; 237 238 AccessibilityObject* parentUnignored = coreObject->parentObjectUnignored(); 239 if (!parentUnignored) 240 return 0; 241 242 Node* node = coreObject->node(); 243 if (!node) 244 return 0; 245 246 Node* parentNode = parentUnignored->node(); 247 if (!parentNode) 248 return 0; 249 250 RefPtr<Range> range = Range::create(node->document(), firstPositionInOrBeforeNode(parentNode), firstPositionInOrBeforeNode(node)); 251 return getRangeLengthForObject(coreObject, range.get()); 252} 253 254static gint webkitAccessibleHyperlinkGetEndIndex(AtkHyperlink* link) 255{ 256 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 257 258 AccessibilityObject* coreObject = core(link); 259 if (!coreObject) 260 return 0; 261 262 AccessibilityObject* parentUnignored = coreObject->parentObjectUnignored(); 263 if (!parentUnignored) 264 return 0; 265 266 Node* node = coreObject->node(); 267 if (!node) 268 return 0; 269 270 Node* parentNode = parentUnignored->node(); 271 if (!parentNode) 272 return 0; 273 274 RefPtr<Range> range = Range::create(node->document(), firstPositionInOrBeforeNode(parentNode), lastPositionInOrAfterNode(node)); 275 return getRangeLengthForObject(coreObject, range.get()); 276} 277 278static gboolean webkitAccessibleHyperlinkIsValid(AtkHyperlink* link) 279{ 280 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 281 g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); 282 283 // Link is valid for the whole object's lifetime 284 return TRUE; 285} 286 287static gint webkitAccessibleHyperlinkGetNAnchors(AtkHyperlink* link) 288{ 289 // FIXME Do NOT support more than one instance of an AtkObject 290 // implementing AtkHyperlinkImpl in every instance of AtkHyperLink 291 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 292 g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); 293 return 1; 294} 295 296static gboolean webkitAccessibleHyperlinkIsSelectedLink(AtkHyperlink*) 297{ 298 // Not implemented: this function is deprecated in ATK now 299 notImplemented(); 300 return FALSE; 301} 302 303static void webkitAccessibleHyperlinkGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* pspec) 304{ 305 switch (propId) { 306 case PROP_HYPERLINK_IMPL: 307 g_value_set_object(value, WEBKIT_ACCESSIBLE_HYPERLINK(object)->priv->hyperlinkImpl); 308 break; 309 default: 310 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); 311 } 312} 313 314static void webkitAccessibleHyperlinkSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) 315{ 316 WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(object)->priv; 317 318 switch (propId) { 319 case PROP_HYPERLINK_IMPL: 320 // No need to check and unref previous values of 321 // priv->hyperlinkImpl as this is a CONSTRUCT ONLY property 322 priv->hyperlinkImpl = WEBKIT_ACCESSIBLE(g_value_get_object(value)); 323 g_object_weak_ref(G_OBJECT(priv->hyperlinkImpl), (GWeakNotify)g_object_unref, object); 324 break; 325 default: 326 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); 327 } 328} 329 330static void webkitAccessibleHyperlinkFinalize(GObject* object) 331{ 332 G_OBJECT_CLASS(webkitAccessibleHyperlinkParentClass)->finalize(object); 333} 334 335static void webkitAccessibleHyperlinkClassInit(AtkHyperlinkClass* klass) 336{ 337 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); 338 339 webkitAccessibleHyperlinkParentClass = g_type_class_peek_parent(klass); 340 341 gobjectClass->finalize = webkitAccessibleHyperlinkFinalize; 342 gobjectClass->set_property = webkitAccessibleHyperlinkSetProperty; 343 gobjectClass->get_property = webkitAccessibleHyperlinkGetProperty; 344 345 klass->get_uri = webkitAccessibleHyperlinkGetURI; 346 klass->get_object = webkitAccessibleHyperlinkGetObject; 347 klass->get_start_index = webkitAccessibleHyperlinkGetStartIndex; 348 klass->get_end_index = webkitAccessibleHyperlinkGetEndIndex; 349 klass->is_valid = webkitAccessibleHyperlinkIsValid; 350 klass->get_n_anchors = webkitAccessibleHyperlinkGetNAnchors; 351 klass->is_selected_link = webkitAccessibleHyperlinkIsSelectedLink; 352 353 g_object_class_install_property(gobjectClass, PROP_HYPERLINK_IMPL, 354 g_param_spec_object("hyperlink-impl", 355 "Hyperlink implementation", 356 "The associated WebKitAccessible instance.", 357 WEBKIT_TYPE_ACCESSIBLE, 358 (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); 359 360 g_type_class_add_private(gobjectClass, sizeof(WebKitAccessibleHyperlinkPrivate)); 361} 362 363static void webkitAccessibleHyperlinkInit(AtkHyperlink* link) 364{ 365 WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv = WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(link); 366 WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = 0; 367} 368 369GType webkitAccessibleHyperlinkGetType() 370{ 371 static volatile gsize typeVolatile = 0; 372 373 if (g_once_init_enter(&typeVolatile)) { 374 static const GTypeInfo tinfo = { 375 sizeof(WebKitAccessibleHyperlinkClass), 376 (GBaseInitFunc) 0, 377 (GBaseFinalizeFunc) 0, 378 (GClassInitFunc) webkitAccessibleHyperlinkClassInit, 379 (GClassFinalizeFunc) 0, 380 0, /* class data */ 381 sizeof(WebKitAccessibleHyperlink), /* instance size */ 382 0, /* nb preallocs */ 383 (GInstanceInitFunc) webkitAccessibleHyperlinkInit, 384 0 /* value table */ 385 }; 386 387 static const GInterfaceInfo actionInfo = { 388 (GInterfaceInitFunc)(GInterfaceInitFunc)atkActionInterfaceInit, 389 (GInterfaceFinalizeFunc) 0, 0 390 }; 391 392 GType type = g_type_register_static(ATK_TYPE_HYPERLINK, "WebKitAccessibleHyperlink", &tinfo, GTypeFlags(0)); 393 g_type_add_interface_static(type, ATK_TYPE_ACTION, &actionInfo); 394 395 g_once_init_leave(&typeVolatile, type); 396 } 397 398 return typeVolatile; 399} 400 401WebKitAccessibleHyperlink* webkitAccessibleHyperlinkNew(AtkHyperlinkImpl* hyperlinkImpl) 402{ 403 g_return_val_if_fail(ATK_IS_HYPERLINK_IMPL(hyperlinkImpl), 0); 404 return WEBKIT_ACCESSIBLE_HYPERLINK(g_object_new(WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, "hyperlink-impl", hyperlinkImpl, 0)); 405} 406 407WebCore::AccessibilityObject* webkitAccessibleHyperlinkGetAccessibilityObject(WebKitAccessibleHyperlink* link) 408{ 409 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); 410 return core(link); 411} 412 413#endif // HAVE(ACCESSIBILITY) 414