OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "config.h" | |
6 | |
7 #include "AccessibilityObject.h" | |
8 #include "EventHandler.h" | |
9 #include "FrameView.h" | |
10 #include "PlatformKeyboardEvent.h" | |
11 | |
12 #include "webkit/glue/glue_accessibility_object.h" | |
13 | |
14 using WebCore::AccessibilityObject; | |
15 using WebCore::String; | |
16 using webkit_glue::WebAccessibility; | |
17 | |
18 GlueAccessibilityObject::GlueAccessibilityObject(AccessibilityObject* obj) | |
19 : AccessibilityObjectWrapper(obj) { | |
20 m_object->setWrapper(this); | |
21 } | |
22 | |
23 GlueAccessibilityObject* GlueAccessibilityObject::CreateInstance( | |
24 AccessibilityObject* obj) { | |
25 if (!obj) | |
26 return NULL; | |
27 | |
28 return new GlueAccessibilityObject(obj); | |
29 } | |
30 | |
31 bool GlueAccessibilityObject::DoDefaultAction(int child_id) { | |
32 AccessibilityObject* child_obj; | |
33 | |
34 if (!GetAccessibilityObjectForChild(child_id, child_obj) || | |
35 !child_obj->performDefaultAction()) { | |
36 return false; | |
37 } | |
38 return true; | |
39 } | |
40 | |
41 GlueAccessibilityObject* GlueAccessibilityObject::HitTest(long x, long y) { | |
42 if (!m_object) | |
43 return NULL; | |
44 | |
45 // x, y - coordinates are passed in as window coordinates, to maintain | |
46 // sandbox functionality. | |
47 WebCore::IntPoint point = | |
48 m_object->documentFrameView()->windowToContents(WebCore::IntPoint(x, y)); | |
49 AccessibilityObject* child_obj = m_object->doAccessibilityHitTest(point); | |
50 | |
51 if (!child_obj) { | |
52 // If we did not hit any child objects, test whether the point hit us, and | |
53 // report that. | |
54 if (!m_object->boundingBoxRect().contains(point)) | |
55 return NULL; | |
56 child_obj = m_object; | |
57 } | |
58 // TODO(klink): simple object child? | |
59 ToWrapper(child_obj)->ref(); | |
60 return ToWrapper(child_obj); | |
61 } | |
62 | |
63 bool GlueAccessibilityObject::Location(long* left, long* top, long* width, | |
64 long* height, int child_id) { | |
65 if (!left || !top || !width || !height) | |
66 return false; | |
67 | |
68 *left = *top = *width = *height = 0; | |
69 | |
70 AccessibilityObject* child_obj; | |
71 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
72 return false; | |
73 | |
74 // Returning window coordinates, to be handled and converted appropriately by | |
75 // the client. | |
76 WebCore::IntRect window_rect(child_obj->documentFrameView()->contentsToWindow( | |
77 child_obj->boundingBoxRect())); | |
78 *left = window_rect.x(); | |
79 *top = window_rect.y(); | |
80 *width = window_rect.width(); | |
81 *height = window_rect.height(); | |
82 return true; | |
83 } | |
84 | |
85 GlueAccessibilityObject* GlueAccessibilityObject::Navigate( | |
86 WebAccessibility::Direction dir, int start_child_id) { | |
87 AccessibilityObject* child_obj = 0; | |
88 | |
89 switch (dir) { | |
90 case WebAccessibility::DIRECTION_DOWN: | |
91 case WebAccessibility::DIRECTION_UP: | |
92 case WebAccessibility::DIRECTION_LEFT: | |
93 case WebAccessibility::DIRECTION_RIGHT: | |
94 // These directions are not implemented, matching Mozilla and IE. | |
95 return NULL; | |
96 case WebAccessibility::DIRECTION_LASTCHILD: | |
97 case WebAccessibility::DIRECTION_FIRSTCHILD: | |
98 // MSDN states that navigating to first/last child can only be from self. | |
99 if (start_child_id != 0 || !m_object) | |
100 return NULL; | |
101 | |
102 if (dir == WebAccessibility::DIRECTION_FIRSTCHILD) { | |
103 child_obj = m_object->firstChild(); | |
104 } else { | |
105 child_obj = m_object->lastChild(); | |
106 } | |
107 break; | |
108 case WebAccessibility::DIRECTION_NEXT: | |
109 case WebAccessibility::DIRECTION_PREVIOUS: { | |
110 // Navigating to next and previous is allowed from self or any of our | |
111 // children. | |
112 if (!GetAccessibilityObjectForChild(start_child_id, child_obj)) | |
113 return NULL; | |
114 | |
115 if (dir == WebAccessibility::DIRECTION_NEXT) { | |
116 child_obj = child_obj->nextSibling(); | |
117 } else { | |
118 child_obj = child_obj->previousSibling(); | |
119 } | |
120 break; | |
121 } | |
122 default: | |
123 return NULL; | |
124 } | |
125 | |
126 if (!child_obj) | |
127 return NULL; | |
128 | |
129 // TODO(klink): simple object child? | |
130 ToWrapper(child_obj)->ref(); | |
131 return ToWrapper(child_obj); | |
132 } | |
133 | |
134 GlueAccessibilityObject* GlueAccessibilityObject::GetChild(int child_id) { | |
135 AccessibilityObject* child_obj; | |
136 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
137 return NULL; | |
138 | |
139 // TODO(klink): simple object child? | |
140 ToWrapper(child_obj)->ref(); | |
141 return ToWrapper(child_obj); | |
142 } | |
143 | |
144 bool GlueAccessibilityObject::ChildCount(long* count) { | |
145 if (!m_object || !count) | |
146 return false; | |
147 | |
148 *count = static_cast<long>(m_object->children().size()); | |
149 return true; | |
150 } | |
151 | |
152 bool GlueAccessibilityObject::DefaultAction(int child_id, String* action) { | |
153 if (!action) | |
154 return false; | |
155 | |
156 AccessibilityObject* child_obj; | |
157 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
158 return false; | |
159 | |
160 *action = child_obj->actionVerb(); | |
161 return !action->isEmpty(); | |
162 } | |
163 | |
164 bool GlueAccessibilityObject::Description(int child_id, String* description) { | |
165 if (!description) | |
166 return false; | |
167 | |
168 AccessibilityObject* child_obj; | |
169 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
170 return false; | |
171 | |
172 // TODO(klink): Description, for SELECT subitems, should be a string | |
173 // describing the position of the item in its group and of the group in the | |
174 // list (see Firefox). | |
175 *description = ToWrapper(child_obj)->description(); | |
176 return !description->isEmpty(); | |
177 } | |
178 | |
179 GlueAccessibilityObject* GlueAccessibilityObject::GetFocusedChild() { | |
180 if (!m_object) | |
181 return NULL; | |
182 | |
183 AccessibilityObject* focused_obj = m_object->focusedUIElement(); | |
184 if (!focused_obj) | |
185 return NULL; | |
186 | |
187 // Only return the focused child if it's us or a child of us. | |
188 if (focused_obj == m_object || focused_obj->parentObject() == m_object) { | |
189 ToWrapper(focused_obj)->ref(); | |
190 return ToWrapper(focused_obj); | |
191 } | |
192 return NULL; | |
193 } | |
194 | |
195 bool GlueAccessibilityObject::HelpText(int child_id, String* help) { | |
196 if (!help) | |
197 return false; | |
198 | |
199 AccessibilityObject* child_obj; | |
200 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
201 return false; | |
202 | |
203 *help = child_obj->helpText(); | |
204 return !help->isEmpty(); | |
205 } | |
206 | |
207 bool GlueAccessibilityObject::KeyboardShortcut(int child_id, String* shortcut) { | |
208 if (!shortcut) | |
209 return false; | |
210 | |
211 AccessibilityObject* child_obj; | |
212 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
213 return false; | |
214 | |
215 String access_key = child_obj->accessKey(); | |
216 if (access_key.isNull()) | |
217 return false; | |
218 | |
219 static String access_key_modifiers; | |
220 if (access_key_modifiers.isNull()) { | |
221 unsigned modifiers = WebCore::EventHandler::accessKeyModifiers(); | |
222 // Follow the same order as Mozilla MSAA implementation: | |
223 // Ctrl+Alt+Shift+Meta+key. MSDN states that keyboard shortcut strings | |
224 // should not be localized and defines the separator as "+". | |
225 if (modifiers & WebCore::PlatformKeyboardEvent::CtrlKey) | |
226 access_key_modifiers += "Ctrl+"; | |
227 if (modifiers & WebCore::PlatformKeyboardEvent::AltKey) | |
228 access_key_modifiers += "Alt+"; | |
229 if (modifiers & WebCore::PlatformKeyboardEvent::ShiftKey) | |
230 access_key_modifiers += "Shift+"; | |
231 if (modifiers & WebCore::PlatformKeyboardEvent::MetaKey) | |
232 access_key_modifiers += "Win+"; | |
233 } | |
234 *shortcut = access_key_modifiers + access_key; | |
235 return !shortcut->isEmpty(); | |
236 } | |
237 | |
238 bool GlueAccessibilityObject::Name(int child_id, String* name) { | |
239 if (!name) | |
240 return false; | |
241 | |
242 AccessibilityObject* child_obj; | |
243 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
244 return false; | |
245 | |
246 *name = ToWrapper(child_obj)->name(); | |
247 return !name->isEmpty(); | |
248 } | |
249 | |
250 GlueAccessibilityObject* GlueAccessibilityObject::GetParent() { | |
251 if (!m_object) | |
252 return NULL; | |
253 | |
254 AccessibilityObject* parent_obj = m_object->parentObject(); | |
255 | |
256 if (parent_obj) { | |
257 ToWrapper(parent_obj)->ref(); | |
258 return ToWrapper(parent_obj); | |
259 } | |
260 // No valid parent, or parent is the containing window. | |
261 return NULL; | |
262 } | |
263 | |
264 bool GlueAccessibilityObject::Role(int child_id, long* role) { | |
265 if (!role) | |
266 return false; | |
267 | |
268 AccessibilityObject* child_obj; | |
269 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
270 return false; | |
271 | |
272 *role = ToWrapper(child_obj)->role(); | |
273 return true; | |
274 } | |
275 | |
276 bool GlueAccessibilityObject::Value(int child_id, String* value) { | |
277 if (!value) | |
278 return false; | |
279 | |
280 AccessibilityObject* child_obj; | |
281 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
282 return false; | |
283 | |
284 *value = ToWrapper(child_obj)->value(); | |
285 return !value->isEmpty(); | |
286 } | |
287 | |
288 bool GlueAccessibilityObject::State(int child_id, long* state) { | |
289 if (!state) | |
290 return false; | |
291 | |
292 *state = 0; | |
293 AccessibilityObject* child_obj; | |
294 if (!GetAccessibilityObjectForChild(child_id, child_obj)) | |
295 return false; | |
296 | |
297 if (child_obj->isChecked()) | |
298 *state |= static_cast<long>(1 << WebAccessibility::STATE_CHECKED); | |
299 | |
300 if (child_obj->canSetFocusAttribute()) | |
301 *state |= static_cast<long>(1 << WebAccessibility::STATE_FOCUSABLE); | |
302 | |
303 if (child_obj->isFocused()) | |
304 *state |= static_cast<long>(1 << WebAccessibility::STATE_FOCUSED); | |
305 | |
306 if (child_obj->isHovered()) | |
307 *state |= static_cast<long>(1 << WebAccessibility::STATE_HOTTRACKED); | |
308 | |
309 if (child_obj->isIndeterminate()) | |
310 *state |= static_cast<long>(1 << WebAccessibility::STATE_INDETERMINATE); | |
311 | |
312 if (child_obj->isAnchor()) | |
313 *state |= static_cast<long>(1 << WebAccessibility::STATE_LINKED); | |
314 | |
315 if (child_obj->isMultiSelect()) | |
316 *state |= static_cast<long>(1 << WebAccessibility::STATE_MULTISELECTABLE); | |
317 | |
318 if (child_obj->isOffScreen()) | |
319 *state |= static_cast<long>(1 << WebAccessibility::STATE_OFFSCREEN); | |
320 | |
321 if (child_obj->isPressed()) | |
322 *state |= static_cast<long>(1 << WebAccessibility::STATE_PRESSED); | |
323 | |
324 if (child_obj->isPasswordField()) | |
325 *state |= static_cast<long>(1 << WebAccessibility::STATE_PROTECTED); | |
326 | |
327 if (child_obj->isReadOnly()) | |
328 *state |= static_cast<long>(1 << WebAccessibility::STATE_READONLY); | |
329 | |
330 if (child_obj->isVisited()) | |
331 *state |= static_cast<long>(1 << WebAccessibility::STATE_TRAVERSED); | |
332 | |
333 if (!child_obj->isEnabled()) | |
334 *state |= static_cast<long>(1 << WebAccessibility::STATE_UNAVAILABLE); | |
335 | |
336 // TODO(klink): Add selected and selectable states. | |
337 | |
338 return true; | |
339 } | |
340 | |
341 // Helper functions | |
342 String GlueAccessibilityObject::name() const { | |
343 return m_object->title(); | |
344 } | |
345 | |
346 String GlueAccessibilityObject::value() const { | |
347 return m_object->stringValue(); | |
348 } | |
349 | |
350 String GlueAccessibilityObject::description() const { | |
351 String desc = m_object->accessibilityDescription(); | |
352 if (desc.isNull()) | |
353 return desc; | |
354 | |
355 // From the Mozilla MSAA implementation: | |
356 // "Signal to screen readers that this description is speakable and is not | |
357 // a formatted positional information description. Don't localize the | |
358 // 'Description: ' part of this string, it will be parsed out by assistive | |
359 // technologies." | |
360 return "Description: " + desc; | |
361 } | |
362 | |
363 // Provides a conversion between the WebCore::AccessibilityRole and a | |
364 // role supported on the Browser side. Listed alphabetically by the | |
365 // WebAccessibility role (except for default role). Static function. | |
366 static WebAccessibility::Role SupportedRole(WebCore::AccessibilityRole role) { | |
367 switch (role) { | |
368 case WebCore::LandmarkApplicationRole: | |
369 return WebAccessibility::ROLE_APPLICATION; | |
370 case WebCore::CellRole: | |
371 return WebAccessibility::ROLE_CELL; | |
372 case WebCore::CheckBoxRole: | |
373 return WebAccessibility::ROLE_CHECKBUTTON; | |
374 case WebCore::ColumnRole: | |
375 return WebAccessibility::ROLE_COLUMN; | |
376 case WebCore::ColumnHeaderRole: | |
377 return WebAccessibility::ROLE_COLUMNHEADER; | |
378 case WebCore::DocumentArticleRole: | |
379 case WebCore::WebAreaRole: | |
380 return WebAccessibility::ROLE_DOCUMENT; | |
381 case WebCore::ImageMapRole: | |
382 case WebCore::ImageRole: | |
383 return WebAccessibility::ROLE_GRAPHIC; | |
384 case WebCore::DocumentRegionRole: | |
385 case WebCore::RadioGroupRole: | |
386 case WebCore::GroupRole: | |
387 return WebAccessibility::ROLE_GROUPING; | |
388 case WebCore::LinkRole: | |
389 case WebCore::WebCoreLinkRole: | |
390 return WebAccessibility::ROLE_LINK; | |
391 case WebCore::ListRole: | |
392 return WebAccessibility::ROLE_LIST; | |
393 case WebCore::ListBoxRole: | |
394 return WebAccessibility::ROLE_LISTBOX; | |
395 case WebCore::ListBoxOptionRole: | |
396 return WebAccessibility::ROLE_LISTITEM; | |
397 case WebCore::MenuBarRole: | |
398 return WebAccessibility::ROLE_MENUBAR; | |
399 case WebCore::MenuButtonRole: | |
400 case WebCore::MenuItemRole: | |
401 return WebAccessibility::ROLE_MENUITEM; | |
402 case WebCore::MenuRole: | |
403 return WebAccessibility::ROLE_MENUPOPUP; | |
404 case WebCore::OutlineRole: | |
405 return WebAccessibility::ROLE_OUTLINE; | |
406 case WebCore::TabGroupRole: | |
407 return WebAccessibility::ROLE_PAGETABLIST; | |
408 case WebCore::ProgressIndicatorRole: | |
409 return WebAccessibility::ROLE_PROGRESSBAR; | |
410 case WebCore::ButtonRole: | |
411 return WebAccessibility::ROLE_PUSHBUTTON; | |
412 case WebCore::RadioButtonRole: | |
413 return WebAccessibility::ROLE_RADIOBUTTON; | |
414 case WebCore::RowRole: | |
415 return WebAccessibility::ROLE_ROW; | |
416 case WebCore::RowHeaderRole: | |
417 return WebAccessibility::ROLE_ROWHEADER; | |
418 case WebCore::SplitterRole: | |
419 return WebAccessibility::ROLE_SEPARATOR; | |
420 case WebCore::SliderRole: | |
421 return WebAccessibility::ROLE_SLIDER; | |
422 case WebCore::StaticTextRole: | |
423 return WebAccessibility::ROLE_STATICTEXT; | |
424 case WebCore::ApplicationStatusRole: | |
425 return WebAccessibility::ROLE_STATUSBAR; | |
426 case WebCore::TableRole: | |
427 return WebAccessibility::ROLE_TABLE; | |
428 case WebCore::ListMarkerRole: | |
429 case WebCore::TextFieldRole: | |
430 case WebCore::TextAreaRole: | |
431 return WebAccessibility::ROLE_TEXT; | |
432 case WebCore::ToolbarRole: | |
433 return WebAccessibility::ROLE_TOOLBAR; | |
434 case WebCore::UserInterfaceTooltipRole: | |
435 return WebAccessibility::ROLE_TOOLTIP; | |
436 case WebCore::DocumentRole: | |
437 case WebCore::UnknownRole: | |
438 default: | |
439 // This is the default role. | |
440 return WebAccessibility::ROLE_CLIENT; | |
441 } | |
442 } | |
443 | |
444 WebAccessibility::Role GlueAccessibilityObject::role() const { | |
445 return SupportedRole(m_object->roleValue()); | |
446 } | |
447 | |
448 bool GlueAccessibilityObject::GetAccessibilityObjectForChild(int child_id, | |
449 AccessibilityObject*& child_obj) const { | |
450 child_obj = 0; | |
451 | |
452 if (!m_object || child_id < 0) | |
453 return false; | |
454 | |
455 if (child_id == 0) { | |
456 child_obj = m_object; | |
457 } else { | |
458 size_t child_index = static_cast<size_t>(child_id - 1); | |
459 | |
460 if (child_index >= m_object->children().size()) | |
461 return false; | |
462 child_obj = m_object->children().at(child_index).get(); | |
463 } | |
464 | |
465 if (!child_obj) | |
466 return false; | |
467 | |
468 return true; | |
469 } | |
470 | |
471 GlueAccessibilityObject* GlueAccessibilityObject::ToWrapper( | |
472 AccessibilityObject* obj) { | |
473 if (!obj) | |
474 return NULL; | |
475 | |
476 GlueAccessibilityObject* result = | |
477 static_cast<GlueAccessibilityObject*>(obj->wrapper()); | |
478 if (!result) | |
479 result = CreateInstance(obj); | |
480 | |
481 return result; | |
482 } | |
OLD | NEW |