// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "config.h" #include "AccessibilityObject.h" #include "EventHandler.h" #include "FrameView.h" #include "PlatformKeyboardEvent.h" #include "webkit/glue/glue_accessibility_object.h" using WebCore::AccessibilityObject; using WebCore::String; using webkit_glue::WebAccessibility; GlueAccessibilityObject::GlueAccessibilityObject(AccessibilityObject* obj) : AccessibilityObjectWrapper(obj) { m_object->setWrapper(this); } GlueAccessibilityObject* GlueAccessibilityObject::CreateInstance( AccessibilityObject* obj) { if (!obj) return NULL; return new GlueAccessibilityObject(obj); } bool GlueAccessibilityObject::DoDefaultAction(int child_id) { AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj) || !child_obj->performDefaultAction()) { return false; } return true; } GlueAccessibilityObject* GlueAccessibilityObject::HitTest(long x, long y) { if (!m_object) return NULL; // x, y - coordinates are passed in as window coordinates, to maintain // sandbox functionality. WebCore::IntPoint point = m_object->documentFrameView()->windowToContents(WebCore::IntPoint(x, y)); AccessibilityObject* child_obj = m_object->doAccessibilityHitTest(point); if (!child_obj) { // If we did not hit any child objects, test whether the point hit us, and // report that. if (!m_object->boundingBoxRect().contains(point)) return NULL; child_obj = m_object; } // TODO(klink): simple object child? ToWrapper(child_obj)->ref(); return ToWrapper(child_obj); } bool GlueAccessibilityObject::Location(long* left, long* top, long* width, long* height, int child_id) { if (!left || !top || !width || !height) return false; *left = *top = *width = *height = 0; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; // Returning window coordinates, to be handled and converted appropriately by // the client. WebCore::IntRect window_rect(child_obj->documentFrameView()->contentsToWindow( child_obj->boundingBoxRect())); *left = window_rect.x(); *top = window_rect.y(); *width = window_rect.width(); *height = window_rect.height(); return true; } GlueAccessibilityObject* GlueAccessibilityObject::Navigate( WebAccessibility::Direction dir, int start_child_id) { AccessibilityObject* child_obj = 0; switch (dir) { case WebAccessibility::DIRECTION_DOWN: case WebAccessibility::DIRECTION_UP: case WebAccessibility::DIRECTION_LEFT: case WebAccessibility::DIRECTION_RIGHT: // These directions are not implemented, matching Mozilla and IE. return NULL; case WebAccessibility::DIRECTION_LASTCHILD: case WebAccessibility::DIRECTION_FIRSTCHILD: // MSDN states that navigating to first/last child can only be from self. if (start_child_id != 0 || !m_object) return NULL; if (dir == WebAccessibility::DIRECTION_FIRSTCHILD) { child_obj = m_object->firstChild(); } else { child_obj = m_object->lastChild(); } break; case WebAccessibility::DIRECTION_NEXT: case WebAccessibility::DIRECTION_PREVIOUS: { // Navigating to next and previous is allowed from self or any of our // children. if (!GetAccessibilityObjectForChild(start_child_id, child_obj)) return NULL; if (dir == WebAccessibility::DIRECTION_NEXT) { child_obj = child_obj->nextSibling(); } else { child_obj = child_obj->previousSibling(); } break; } default: return NULL; } if (!child_obj) return NULL; // TODO(klink): simple object child? ToWrapper(child_obj)->ref(); return ToWrapper(child_obj); } GlueAccessibilityObject* GlueAccessibilityObject::GetChild(int child_id) { AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return NULL; // TODO(klink): simple object child? ToWrapper(child_obj)->ref(); return ToWrapper(child_obj); } bool GlueAccessibilityObject::ChildCount(long* count) { if (!m_object || !count) return false; *count = static_cast(m_object->children().size()); return true; } bool GlueAccessibilityObject::DefaultAction(int child_id, String* action) { if (!action) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; *action = child_obj->actionVerb(); return !action->isEmpty(); } bool GlueAccessibilityObject::Description(int child_id, String* description) { if (!description) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; // TODO(klink): Description, for SELECT subitems, should be a string // describing the position of the item in its group and of the group in the // list (see Firefox). *description = ToWrapper(child_obj)->description(); return !description->isEmpty(); } GlueAccessibilityObject* GlueAccessibilityObject::GetFocusedChild() { if (!m_object) return NULL; AccessibilityObject* focused_obj = m_object->focusedUIElement(); if (!focused_obj) return NULL; // Only return the focused child if it's us or a child of us. if (focused_obj == m_object || focused_obj->parentObject() == m_object) { ToWrapper(focused_obj)->ref(); return ToWrapper(focused_obj); } return NULL; } bool GlueAccessibilityObject::HelpText(int child_id, String* help) { if (!help) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; *help = child_obj->helpText(); return !help->isEmpty(); } bool GlueAccessibilityObject::KeyboardShortcut(int child_id, String* shortcut) { if (!shortcut) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; String access_key = child_obj->accessKey(); if (access_key.isNull()) return false; static String access_key_modifiers; if (access_key_modifiers.isNull()) { unsigned modifiers = WebCore::EventHandler::accessKeyModifiers(); // Follow the same order as Mozilla MSAA implementation: // Ctrl+Alt+Shift+Meta+key. MSDN states that keyboard shortcut strings // should not be localized and defines the separator as "+". if (modifiers & WebCore::PlatformKeyboardEvent::CtrlKey) access_key_modifiers += "Ctrl+"; if (modifiers & WebCore::PlatformKeyboardEvent::AltKey) access_key_modifiers += "Alt+"; if (modifiers & WebCore::PlatformKeyboardEvent::ShiftKey) access_key_modifiers += "Shift+"; if (modifiers & WebCore::PlatformKeyboardEvent::MetaKey) access_key_modifiers += "Win+"; } *shortcut = access_key_modifiers + access_key; return !shortcut->isEmpty(); } bool GlueAccessibilityObject::Name(int child_id, String* name) { if (!name) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; *name = ToWrapper(child_obj)->name(); return !name->isEmpty(); } GlueAccessibilityObject* GlueAccessibilityObject::GetParent() { if (!m_object) return NULL; AccessibilityObject* parent_obj = m_object->parentObject(); if (parent_obj) { ToWrapper(parent_obj)->ref(); return ToWrapper(parent_obj); } // No valid parent, or parent is the containing window. return NULL; } bool GlueAccessibilityObject::Role(int child_id, long* role) { if (!role) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; *role = ToWrapper(child_obj)->role(); return true; } bool GlueAccessibilityObject::Value(int child_id, String* value) { if (!value) return false; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; *value = ToWrapper(child_obj)->value(); return !value->isEmpty(); } bool GlueAccessibilityObject::State(int child_id, long* state) { if (!state) return false; *state = 0; AccessibilityObject* child_obj; if (!GetAccessibilityObjectForChild(child_id, child_obj)) return false; if (child_obj->isChecked()) *state |= static_cast(1 << WebAccessibility::STATE_CHECKED); if (child_obj->canSetFocusAttribute()) *state |= static_cast(1 << WebAccessibility::STATE_FOCUSABLE); if (child_obj->isFocused()) *state |= static_cast(1 << WebAccessibility::STATE_FOCUSED); if (child_obj->isHovered()) *state |= static_cast(1 << WebAccessibility::STATE_HOTTRACKED); if (child_obj->isIndeterminate()) *state |= static_cast(1 << WebAccessibility::STATE_INDETERMINATE); if (child_obj->isAnchor()) *state |= static_cast(1 << WebAccessibility::STATE_LINKED); if (child_obj->isMultiSelect()) *state |= static_cast(1 << WebAccessibility::STATE_MULTISELECTABLE); if (child_obj->isOffScreen()) *state |= static_cast(1 << WebAccessibility::STATE_OFFSCREEN); if (child_obj->isPressed()) *state |= static_cast(1 << WebAccessibility::STATE_PRESSED); if (child_obj->isPasswordField()) *state |= static_cast(1 << WebAccessibility::STATE_PROTECTED); if (child_obj->isReadOnly()) *state |= static_cast(1 << WebAccessibility::STATE_READONLY); if (child_obj->isVisited()) *state |= static_cast(1 << WebAccessibility::STATE_TRAVERSED); if (!child_obj->isEnabled()) *state |= static_cast(1 << WebAccessibility::STATE_UNAVAILABLE); // TODO(klink): Add selected and selectable states. return true; } // Helper functions String GlueAccessibilityObject::name() const { return m_object->title(); } String GlueAccessibilityObject::value() const { return m_object->stringValue(); } String GlueAccessibilityObject::description() const { String desc = m_object->accessibilityDescription(); if (desc.isNull()) return desc; // From the Mozilla MSAA implementation: // "Signal to screen readers that this description is speakable and is not // a formatted positional information description. Don't localize the // 'Description: ' part of this string, it will be parsed out by assistive // technologies." return "Description: " + desc; } // Provides a conversion between the WebCore::AccessibilityRole and a // role supported on the Browser side. Listed alphabetically by the // WebAccessibility role (except for default role). Static function. static WebAccessibility::Role SupportedRole(WebCore::AccessibilityRole role) { switch (role) { case WebCore::LandmarkApplicationRole: return WebAccessibility::ROLE_APPLICATION; case WebCore::CellRole: return WebAccessibility::ROLE_CELL; case WebCore::CheckBoxRole: return WebAccessibility::ROLE_CHECKBUTTON; case WebCore::ColumnRole: return WebAccessibility::ROLE_COLUMN; case WebCore::ColumnHeaderRole: return WebAccessibility::ROLE_COLUMNHEADER; case WebCore::DocumentArticleRole: case WebCore::WebAreaRole: return WebAccessibility::ROLE_DOCUMENT; case WebCore::ImageMapRole: case WebCore::ImageRole: return WebAccessibility::ROLE_GRAPHIC; case WebCore::DocumentRegionRole: case WebCore::RadioGroupRole: case WebCore::GroupRole: return WebAccessibility::ROLE_GROUPING; case WebCore::LinkRole: case WebCore::WebCoreLinkRole: return WebAccessibility::ROLE_LINK; case WebCore::ListRole: return WebAccessibility::ROLE_LIST; case WebCore::ListBoxRole: return WebAccessibility::ROLE_LISTBOX; case WebCore::ListBoxOptionRole: return WebAccessibility::ROLE_LISTITEM; case WebCore::MenuBarRole: return WebAccessibility::ROLE_MENUBAR; case WebCore::MenuButtonRole: case WebCore::MenuItemRole: return WebAccessibility::ROLE_MENUITEM; case WebCore::MenuRole: return WebAccessibility::ROLE_MENUPOPUP; case WebCore::OutlineRole: return WebAccessibility::ROLE_OUTLINE; case WebCore::TabGroupRole: return WebAccessibility::ROLE_PAGETABLIST; case WebCore::ProgressIndicatorRole: return WebAccessibility::ROLE_PROGRESSBAR; case WebCore::ButtonRole: return WebAccessibility::ROLE_PUSHBUTTON; case WebCore::RadioButtonRole: return WebAccessibility::ROLE_RADIOBUTTON; case WebCore::RowRole: return WebAccessibility::ROLE_ROW; case WebCore::RowHeaderRole: return WebAccessibility::ROLE_ROWHEADER; case WebCore::SplitterRole: return WebAccessibility::ROLE_SEPARATOR; case WebCore::SliderRole: return WebAccessibility::ROLE_SLIDER; case WebCore::StaticTextRole: return WebAccessibility::ROLE_STATICTEXT; case WebCore::ApplicationStatusRole: return WebAccessibility::ROLE_STATUSBAR; case WebCore::TableRole: return WebAccessibility::ROLE_TABLE; case WebCore::ListMarkerRole: case WebCore::TextFieldRole: case WebCore::TextAreaRole: return WebAccessibility::ROLE_TEXT; case WebCore::ToolbarRole: return WebAccessibility::ROLE_TOOLBAR; case WebCore::UserInterfaceTooltipRole: return WebAccessibility::ROLE_TOOLTIP; case WebCore::DocumentRole: case WebCore::UnknownRole: default: // This is the default role. return WebAccessibility::ROLE_CLIENT; } } WebAccessibility::Role GlueAccessibilityObject::role() const { return SupportedRole(m_object->roleValue()); } bool GlueAccessibilityObject::GetAccessibilityObjectForChild(int child_id, AccessibilityObject*& child_obj) const { child_obj = 0; if (!m_object || child_id < 0) return false; if (child_id == 0) { child_obj = m_object; } else { size_t child_index = static_cast(child_id - 1); if (child_index >= m_object->children().size()) return false; child_obj = m_object->children().at(child_index).get(); } if (!child_obj) return false; return true; } GlueAccessibilityObject* GlueAccessibilityObject::ToWrapper( AccessibilityObject* obj) { if (!obj) return NULL; GlueAccessibilityObject* result = static_cast(obj->wrapper()); if (!result) result = CreateInstance(obj); return result; }