// Copyright (c) 2010 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 "views/accessibility/view_accessibility.h"

#include "views/accessibility/view_accessibility_wrapper.h"
#include "views/widget/widget.h"
#include "views/widget/widget_win.h"

const wchar_t kViewsUninitializeAccessibilityInstance[] =
  L"Views_Uninitialize_AccessibilityInstance";

const wchar_t kViewsNativeHostPropForAccessibility[] =
  L"Views_NativeViewHostHWNDForAccessibility";


HRESULT ViewAccessibility::Initialize(views::View* view) {
  if (!view) {
    return E_INVALIDARG;
  }

  view_ = view;
  return S_OK;
}

// TODO(ctguil): Handle case where child View is not contained by parent.
STDMETHODIMP ViewAccessibility::accHitTest(LONG x_left, LONG y_top,
                                           VARIANT* child) {
  if (!child) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  gfx::Point pt(x_left, y_top);
  views::View::ConvertPointToView(NULL, view_, &pt);

  if (!view_->HitTest(pt)) {
    // If containing parent is not hit, return with failure.
    child->vt = VT_EMPTY;
    return S_FALSE;
  }

  int child_count = view_->GetChildViewCount();
  bool child_hit = false;
  views::View* child_view = NULL;
  for (int child_id = 0; child_id < child_count; ++child_id) {
    // Search for hit within any of the children.
    child_view = view_->GetChildViewAt(child_id);
    views::View::ConvertPointToView(view_, child_view, &pt);
    if (child_view->HitTest(pt)) {
      // Store child_id (adjusted with +1 to convert to MSAA indexing).
      child->lVal = child_id + 1;
      child_hit = true;
      break;
    }
    // Convert point back to parent view to test next child.
    views::View::ConvertPointToView(child_view, view_, &pt);
  }

  child->vt = VT_I4;

  if (!child_hit) {
    // No child hit, return parent id.
    child->lVal = CHILDID_SELF;
  } else {
    if (child_view == NULL) {
      return E_FAIL;
    } else if (child_view->GetChildViewCount() != 0) {
      // Retrieve IDispatch for child, if it is not a leaf.
      child->vt = VT_DISPATCH;
      if ((GetViewAccessibilityWrapper(child_view))->
          GetInstance(IID_IAccessible,
                      reinterpret_cast<void**>(&child->pdispVal)) == S_OK) {
        // Increment the reference count for the retrieved interface.
        child->pdispVal->AddRef();
        return S_OK;
      } else {
        return E_NOINTERFACE;
      }
    }
  }

  return S_OK;
}

STDMETHODIMP ViewAccessibility::accLocation(LONG* x_left, LONG* y_top,
                                            LONG* width, LONG* height,
                                            VARIANT var_id) {
  if (var_id.vt != VT_I4 || !x_left || !y_top || !width || !height) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  gfx::Rect view_bounds;
  // Retrieving the parent View to be used for converting from view-to-screen
  // coordinates.
  views::View* parent = view_->GetParent();

  if (parent == NULL) {
    // If no parent, remain within the same View.
    parent = view_;
  }

  if (var_id.lVal == CHILDID_SELF) {
    // Retrieve active View's bounds.
    view_bounds = view_->bounds();
  } else {
    // Check to see if child is out-of-bounds.
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    // Retrieve child bounds.
    view_bounds = view_->GetChildViewAt(var_id.lVal - 1)->bounds();
    // Parent View is current View.
    parent = view_;
  }

  if (!view_bounds.IsEmpty()) {
    *width  = view_bounds.width();
    *height = view_bounds.height();

    gfx::Point topleft(view_bounds.origin());
    views::View::ConvertPointToScreen(parent, &topleft);
    *x_left = topleft.x();
    *y_top  = topleft.y();
  } else {
    return E_FAIL;
  }
  return S_OK;
}

STDMETHODIMP ViewAccessibility::accNavigate(LONG nav_dir, VARIANT start,
                                            VARIANT* end) {
  if (start.vt != VT_I4 || !end) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  switch (nav_dir) {
    case NAVDIR_FIRSTCHILD:
    case NAVDIR_LASTCHILD: {
      if (start.lVal != CHILDID_SELF) {
        // Start of navigation must be on the View itself.
        return E_INVALIDARG;
      } else if (view_->GetChildViewCount() == 0) {
        // No children found.
        return S_FALSE;
      }

      // Set child_id based on first or last child.
      int child_id = 0;
      if (nav_dir == NAVDIR_LASTCHILD) {
        child_id = view_->GetChildViewCount() - 1;
      }

      views::View* child = view_->GetChildViewAt(child_id);

      if (child->GetChildViewCount() != 0) {
        end->vt = VT_DISPATCH;
        if ((GetViewAccessibilityWrapper(child))->
            GetInstance(IID_IAccessible,
                        reinterpret_cast<void**>(&end->pdispVal)) == S_OK) {
          // Increment the reference count for the retrieved interface.
          end->pdispVal->AddRef();
          return S_OK;
        } else {
          return E_NOINTERFACE;
        }
      } else {
        end->vt = VT_I4;
        // Set return child lVal, adjusted for MSAA indexing. (MSAA
        // child indexing starts with 1, whereas View indexing starts with 0).
        end->lVal = child_id + 1;
      }
      break;
    }
    case NAVDIR_LEFT:
    case NAVDIR_UP:
    case NAVDIR_PREVIOUS:
    case NAVDIR_RIGHT:
    case NAVDIR_DOWN:
    case NAVDIR_NEXT: {
      // Retrieve parent to access view index and perform bounds checking.
      views::View* parent = view_->GetParent();
      if (!parent) {
        return E_FAIL;
      }

      if (start.lVal == CHILDID_SELF) {
        int view_index = parent->GetChildIndex(view_);
        // Check navigation bounds, adjusting for View child indexing (MSAA
        // child indexing starts with 1, whereas View indexing starts with 0).
        if (!IsValidNav(nav_dir, view_index, -1,
                        parent->GetChildViewCount() - 1)) {
          // Navigation attempted to go out-of-bounds.
          end->vt = VT_EMPTY;
          return S_FALSE;
        } else {
          if (IsNavDirNext(nav_dir)) {
            view_index += 1;
          } else {
            view_index -=1;
          }
        }

        views::View* child = parent->GetChildViewAt(view_index);
        if (child->GetChildViewCount() != 0) {
          end->vt = VT_DISPATCH;
          // Retrieve IDispatch for non-leaf child.
          if ((GetViewAccessibilityWrapper(child))->
              GetInstance(IID_IAccessible,
                          reinterpret_cast<void**>(&end->pdispVal)) == S_OK) {
            // Increment the reference count for the retrieved interface.
            end->pdispVal->AddRef();
            return S_OK;
          } else {
            return E_NOINTERFACE;
          }
        } else {
          end->vt = VT_I4;
          // Modifying view_index to give lVal correct MSAA-based value. (MSAA
          // child indexing starts with 1, whereas View indexing starts with 0).
          end->lVal = view_index + 1;
        }
      } else {
        // Check navigation bounds, adjusting for MSAA child indexing (MSAA
        // child indexing starts with 1, whereas View indexing starts with 0).
        if (!IsValidNav(nav_dir, start.lVal, 0,
                        parent->GetChildViewCount() + 1)) {
            // Navigation attempted to go out-of-bounds.
            end->vt = VT_EMPTY;
            return S_FALSE;
          } else {
            if (IsNavDirNext(nav_dir)) {
              start.lVal += 1;
            } else {
              start.lVal -= 1;
            }
        }

        HRESULT result = this->get_accChild(start, &end->pdispVal);
        if (result == S_FALSE) {
          // Child is a leaf.
          end->vt = VT_I4;
          end->lVal = start.lVal;
        } else if (result == E_INVALIDARG) {
          return E_INVALIDARG;
        } else {
          // Child is not a leaf.
          end->vt = VT_DISPATCH;
        }
      }
      break;
    }
    default:
      return E_INVALIDARG;
  }
  // Navigation performed correctly. Global return for this function, if no
  // error triggered an escape earlier.
  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accChild(VARIANT var_child,
                                             IDispatch** disp_child) {
  if (var_child.vt != VT_I4 || !disp_child) {
    return E_INVALIDARG;
  }

  // If var_child is the parent, remain with the same IDispatch.
  if (var_child.lVal == CHILDID_SELF) {
    return S_OK;
  }

  if (!view_) {
    return E_FAIL;
  }

  views::View* child_view = NULL;

  // Check to see if child is out-of-bounds.
  if (IsValidChild((var_child.lVal - 1), view_)) {
    child_view = view_->GetChildViewAt(var_child.lVal - 1);

    // Parents handle leaf IAccessible's.
    if (child_view && child_view->GetChildViewCount() == 0 &&
        !child_view->child_widget())
      return S_FALSE;
  } else {
    // Child is located elsewhere in this view's subtree.
    // Positive child id's are 1-based indexes so you can iterate over all
    // children, and negative values are direct references to objects.
    // Note that we return full IAccessible's for leafs that have
    // negative child id's.
    if (var_child.lVal > 0) {
      child_view = view_->GetViewByID(static_cast<int>(var_child.lVal));
    } else {
      // Retrieve it from our cache of views that have fired events.
      views::WidgetWin* view_widget =
          static_cast<views::WidgetWin*>(view_->GetWidget());
      child_view = view_widget->GetAccessibilityViewEventAt(var_child.lVal);
    }
  }

  if (!child_view) {
    // No child found.
    *disp_child = NULL;
    return E_FAIL;
  }

  // First, check to see if the child is a native view.
  if (child_view->GetClassName() == views::NativeViewHost::kViewClassName) {
    views::NativeViewHost* native_host =
        static_cast<views::NativeViewHost*>(child_view);
    if (GetNativeIAccessibleInterface(native_host, disp_child) == S_OK)
      return S_OK;
  }

  // Next, see if the child view is a widget container.
  if (child_view->child_widget()) {
    views::WidgetWin* native_widget =
      reinterpret_cast<views::WidgetWin*>(child_view->child_widget());
    if (GetNativeIAccessibleInterface(
        native_widget->GetNativeView(), disp_child) == S_OK) {
      return S_OK;
    }
  }

  // Finally, try our ViewAccessibility implementation.
  // Retrieve the IUnknown interface for the requested child view, and
  // assign the IDispatch returned.
  HRESULT hr = GetViewAccessibilityWrapper(child_view)->
      GetInstance(IID_IAccessible, reinterpret_cast<void**>(disp_child));
  if (hr == S_OK) {
    // Increment the reference count for the retrieved interface.
    (*disp_child)->AddRef();
    return S_OK;
  } else {
    return E_NOINTERFACE;
  }
}

STDMETHODIMP ViewAccessibility::get_accChildCount(LONG* child_count) {
  if (!child_count || !view_) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  *child_count = view_->GetChildViewCount();
  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accDefaultAction(VARIANT var_id,
                                                     BSTR* def_action) {
  if (var_id.vt != VT_I4 || !def_action) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  std::wstring temp_action;

  if (var_id.lVal == CHILDID_SELF) {
    view_->GetAccessibleDefaultAction(&temp_action);
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    view_->GetChildViewAt(var_id.lVal - 1)->
        GetAccessibleDefaultAction(&temp_action);
  }
  if (!temp_action.empty()) {
    *def_action = SysAllocString(temp_action.c_str());
  } else {
    return S_FALSE;
  }

  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accDescription(VARIANT var_id, BSTR* desc) {
  if (var_id.vt != VT_I4 || !desc) {
    return E_INVALIDARG;
  }

  std::wstring temp_desc;

  if (var_id.lVal == CHILDID_SELF) {
    view_->GetTooltipText(gfx::Point(), &temp_desc);
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    view_->GetChildViewAt(var_id.lVal - 1)->GetTooltipText(gfx::Point(),
                                                           &temp_desc);
  }
  if (!temp_desc.empty()) {
    *desc = SysAllocString(temp_desc.c_str());
  } else {
    return S_FALSE;
  }

  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accFocus(VARIANT* focus_child) {
  if (!focus_child) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  if (view_->GetChildViewCount() == 0 && view_->HasFocus()) {
    // Parent view has focus.
    focus_child->vt = VT_I4;
    focus_child->lVal = CHILDID_SELF;
  } else {
    bool has_focus = false;
    int child_count = view_->GetChildViewCount();
    // Search for child view with focus.
    for (int child_id = 0; child_id < child_count; ++child_id) {
      if (view_->GetChildViewAt(child_id)->HasFocus()) {
        focus_child->vt = VT_I4;
        focus_child->lVal = child_id + 1;

        // If child view is no leaf, retrieve IDispatch.
        if (view_->GetChildViewAt(child_id)->GetChildViewCount() != 0) {
          focus_child->vt = VT_DISPATCH;
          this->get_accChild(*focus_child, &focus_child->pdispVal);
        }
        has_focus = true;
        break;
      }
    }
    // No current focus on any of the children.
    if (!has_focus) {
      focus_child->vt = VT_EMPTY;
      return S_FALSE;
    }
  }

  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accKeyboardShortcut(VARIANT var_id,
                                                        BSTR* acc_key) {
  if (var_id.vt != VT_I4 || !acc_key) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  std::wstring temp_key;

  if (var_id.lVal == CHILDID_SELF) {
    view_->GetAccessibleKeyboardShortcut(&temp_key);
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    view_->GetChildViewAt(var_id.lVal - 1)->
        GetAccessibleKeyboardShortcut(&temp_key);
  }
  if (!temp_key.empty()) {
    *acc_key = SysAllocString(temp_key.c_str());
  } else {
    return S_FALSE;
  }

  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accName(VARIANT var_id, BSTR* name) {
  if (var_id.vt != VT_I4 || !name) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  std::wstring temp_name;

  if (var_id.lVal == CHILDID_SELF) {
    // Retrieve the current view's name.
    view_->GetAccessibleName(&temp_name);
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    // Retrieve the child view's name.
    view_->GetChildViewAt(var_id.lVal - 1)->GetAccessibleName(&temp_name);
  }
  if (!temp_name.empty()) {
    // Return name retrieved.
    *name = SysAllocString(temp_name.c_str());
  } else {
    // If view has no name, return S_FALSE.
    return S_FALSE;
  }

  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accParent(IDispatch** disp_parent) {
  if (!disp_parent) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  views::View* parent_view = view_->GetParent();

  if (!parent_view) {
    // This function can get called during teardown of WidetWin so we
    // should bail out if we fail to get the HWND.
    if (!view_->GetWidget() || !view_->GetWidget()->GetNativeView()) {
      *disp_parent = NULL;
      return S_FALSE;
    }

    // For a View that has no parent (e.g. root), point the accessible parent to
    // the default implementation, to interface with Windows' hierarchy and to
    // support calls from e.g. WindowFromAccessibleObject.
    HRESULT hr =
        ::AccessibleObjectFromWindow(view_->GetWidget()->GetNativeView(),
                                     OBJID_WINDOW, IID_IAccessible,
                                     reinterpret_cast<void**>(disp_parent));

    if (!SUCCEEDED(hr)) {
      *disp_parent = NULL;
      return S_FALSE;
    }

    return S_OK;
  }

  // Retrieve the IUnknown interface for the parent view, and assign the
  // IDispatch returned.
  if ((GetViewAccessibilityWrapper(parent_view))->
      GetInstance(IID_IAccessible,
                  reinterpret_cast<void**>(disp_parent)) == S_OK) {
    // Increment the reference count for the retrieved interface.
    (*disp_parent)->AddRef();
    return S_OK;
  } else {
    return E_NOINTERFACE;
  }
}

STDMETHODIMP ViewAccessibility::get_accRole(VARIANT var_id, VARIANT* role) {
  if (var_id.vt != VT_I4 || !role) {
    return E_INVALIDARG;
  }

  AccessibilityTypes::Role acc_role;

  if (var_id.lVal == CHILDID_SELF) {
    // Retrieve parent role.
    if (!view_->GetAccessibleRole(&acc_role)) {
      return E_FAIL;
    }
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    // Retrieve child role.
    if (!view_->GetChildViewAt(var_id.lVal - 1)->GetAccessibleRole(&acc_role)) {
      role->vt = VT_EMPTY;
      return E_FAIL;
    }
  }

  role->vt = VT_I4;
  role->lVal = MSAARole(acc_role);
  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accState(VARIANT var_id, VARIANT* state) {
  if (var_id.vt != VT_I4 || !state) {
    return E_INVALIDARG;
  }

  state->vt = VT_I4;

  if (var_id.lVal == CHILDID_SELF) {
    // Retrieve all currently applicable states of the parent.
    this->SetState(state, view_);
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    // Retrieve all currently applicable states of the child.
    this->SetState(state, view_->GetChildViewAt(var_id.lVal - 1));
  }

  // Make sure that state is not empty, and has the proper type.
  if (state->vt == VT_EMPTY)
    return E_FAIL;

  return S_OK;
}

STDMETHODIMP ViewAccessibility::get_accValue(VARIANT var_id, BSTR* value) {
  if (var_id.vt != VT_I4 || !value) {
    return E_INVALIDARG;
  }

  if (!view_) {
    return E_FAIL;
  }

  std::wstring temp_value;

  if (var_id.lVal == CHILDID_SELF) {
    // Retrieve the current view's value.
    view_->GetAccessibleValue(&temp_value);
  } else {
    if (!IsValidChild((var_id.lVal - 1), view_)) {
      return E_INVALIDARG;
    }
    // Retrieve the child view's value.
    view_->GetChildViewAt(var_id.lVal - 1)->GetAccessibleValue(&temp_value);
  }
  if (!temp_value.empty()) {
    // Return value retrieved.
    *value = SysAllocString(temp_value.c_str());
  } else {
    // If view has no value, fall back into the default implementation.
    *value = NULL;
    return E_NOTIMPL;
  }

  return S_OK;
}

// Helper functions.

bool ViewAccessibility::IsValidChild(int child_id, views::View* view) const {
  if (((child_id) < 0) ||
      ((child_id) >= view->GetChildViewCount())) {
    return false;
  }
  return true;
}

bool ViewAccessibility::IsNavDirNext(int nav_dir) const {
  if (nav_dir == NAVDIR_RIGHT || nav_dir == NAVDIR_DOWN ||
      nav_dir == NAVDIR_NEXT) {
      return true;
  }
  return false;
}

bool ViewAccessibility::IsValidNav(int nav_dir, int start_id, int lower_bound,
                                   int upper_bound) const {
  if (IsNavDirNext(nav_dir)) {
    if ((start_id + 1) > upper_bound) {
      return false;
    }
  } else {
    if ((start_id - 1) <= lower_bound) {
      return false;
    }
  }
  return true;
}

void ViewAccessibility::SetState(VARIANT* msaa_state, views::View* view) {
  // Ensure the output param is initialized to zero.
  msaa_state->lVal = 0;

  // Default state; all views can have accessibility focus.
  msaa_state->lVal |= STATE_SYSTEM_FOCUSABLE;

  if (!view)
    return;

  if (!view->IsEnabled()) {
    msaa_state->lVal |= STATE_SYSTEM_UNAVAILABLE;
  }
  if (!view->IsVisible()) {
    msaa_state->lVal |= STATE_SYSTEM_INVISIBLE;
  }
  if (view->IsHotTracked()) {
    msaa_state->lVal |= STATE_SYSTEM_HOTTRACKED;
  }
  if (view->IsPushed()) {
    msaa_state->lVal |= STATE_SYSTEM_PRESSED;
  }
  // Check both for actual View focus, as well as accessibility focus.
  views::View* parent = view->GetParent();

  if (view->HasFocus())
    msaa_state->lVal |= STATE_SYSTEM_FOCUSED;

  // Add on any view-specific states.
  AccessibilityTypes::State state;
  if (view->GetAccessibleState(&state))
    msaa_state->lVal |= MSAAState(state);
}

// IAccessible functions not supported.

HRESULT ViewAccessibility::accDoDefaultAction(VARIANT var_id) {
  return E_NOTIMPL;
}

STDMETHODIMP ViewAccessibility::get_accSelection(VARIANT* selected) {
  if (selected)
    selected->vt = VT_EMPTY;
  return E_NOTIMPL;
}

STDMETHODIMP ViewAccessibility::accSelect(LONG flagsSelect, VARIANT var_id) {
  return E_NOTIMPL;
}

STDMETHODIMP ViewAccessibility::get_accHelp(VARIANT var_id, BSTR* help) {
  if (help)
    *help = NULL;
  return E_NOTIMPL;
}

STDMETHODIMP ViewAccessibility::get_accHelpTopic(BSTR* help_file,
                                                 VARIANT var_id,
                                                 LONG* topic_id) {
  if (help_file) {
    *help_file = NULL;
  }
  if (topic_id) {
    *topic_id = static_cast<LONG>(-1);
  }
  return E_NOTIMPL;
}

STDMETHODIMP ViewAccessibility::put_accName(VARIANT var_id, BSTR put_name) {
  // Deprecated.
  return E_NOTIMPL;
}

STDMETHODIMP ViewAccessibility::put_accValue(VARIANT var_id, BSTR put_val) {
  if (V_VT(&var_id) == VT_BSTR) {
    if (!lstrcmpi(var_id.bstrVal, kViewsUninitializeAccessibilityInstance)) {
      view_ = NULL;
      return S_OK;
    }
  }
  // Deprecated.
  return E_NOTIMPL;
}

int32 ViewAccessibility::MSAAEvent(AccessibilityTypes::Event event) {
  switch (event) {
    case AccessibilityTypes::EVENT_FOCUS:
      return EVENT_OBJECT_FOCUS;
    case AccessibilityTypes::EVENT_MENUSTART:
      return EVENT_SYSTEM_MENUSTART;
    case AccessibilityTypes::EVENT_MENUEND:
      return EVENT_SYSTEM_MENUEND;
    case AccessibilityTypes::EVENT_MENUPOPUPSTART:
      return EVENT_SYSTEM_MENUPOPUPSTART;
    case AccessibilityTypes::EVENT_MENUPOPUPEND:
      return EVENT_SYSTEM_MENUPOPUPEND;
    default:
      // Not supported or invalid event.
      NOTREACHED();
      return -1;
  }
}

int32 ViewAccessibility::MSAARole(AccessibilityTypes::Role role) {
  switch (role) {
    case AccessibilityTypes::ROLE_APPLICATION:
      return ROLE_SYSTEM_APPLICATION;
    case AccessibilityTypes::ROLE_BUTTONDROPDOWN:
      return ROLE_SYSTEM_BUTTONDROPDOWN;
    case AccessibilityTypes::ROLE_BUTTONMENU:
      return ROLE_SYSTEM_BUTTONMENU;
    case AccessibilityTypes::ROLE_CHECKBUTTON:
      return ROLE_SYSTEM_CHECKBUTTON;
    case AccessibilityTypes::ROLE_COMBOBOX:
      return ROLE_SYSTEM_COMBOBOX;
    case AccessibilityTypes::ROLE_DIALOG:
      return ROLE_SYSTEM_DIALOG;
    case AccessibilityTypes::ROLE_GRAPHIC:
      return ROLE_SYSTEM_GRAPHIC;
    case AccessibilityTypes::ROLE_GROUPING:
      return ROLE_SYSTEM_GROUPING;
    case AccessibilityTypes::ROLE_LINK:
      return ROLE_SYSTEM_LINK;
    case AccessibilityTypes::ROLE_MENUITEM:
      return ROLE_SYSTEM_MENUITEM;
    case AccessibilityTypes::ROLE_MENUPOPUP:
      return ROLE_SYSTEM_MENUPOPUP;
    case AccessibilityTypes::ROLE_OUTLINE:
      return ROLE_SYSTEM_OUTLINE;
    case AccessibilityTypes::ROLE_OUTLINEITEM:
      return ROLE_SYSTEM_OUTLINEITEM;
    case AccessibilityTypes::ROLE_PAGETAB:
      return ROLE_SYSTEM_PAGETAB;
    case AccessibilityTypes::ROLE_PAGETABLIST:
      return ROLE_SYSTEM_PAGETABLIST;
    case AccessibilityTypes::ROLE_PANE:
      return ROLE_SYSTEM_PANE;
    case AccessibilityTypes::ROLE_PROGRESSBAR:
      return ROLE_SYSTEM_PROGRESSBAR;
    case AccessibilityTypes::ROLE_PUSHBUTTON:
      return ROLE_SYSTEM_PUSHBUTTON;
    case AccessibilityTypes::ROLE_RADIOBUTTON:
      return ROLE_SYSTEM_RADIOBUTTON;
    case AccessibilityTypes::ROLE_SCROLLBAR:
      return ROLE_SYSTEM_SCROLLBAR;
    case AccessibilityTypes::ROLE_SEPARATOR:
      return ROLE_SYSTEM_SEPARATOR;
    case AccessibilityTypes::ROLE_STATICTEXT:
      return ROLE_SYSTEM_STATICTEXT;
    case AccessibilityTypes::ROLE_TEXT:
      return ROLE_SYSTEM_TEXT;
    case AccessibilityTypes::ROLE_TITLEBAR:
      return ROLE_SYSTEM_TITLEBAR;
    case AccessibilityTypes::ROLE_TOOLBAR:
      return ROLE_SYSTEM_TOOLBAR;
    case AccessibilityTypes::ROLE_WINDOW:
      return ROLE_SYSTEM_WINDOW;
    case AccessibilityTypes::ROLE_CLIENT:
    default:
      // This is the default role for MSAA.
      return ROLE_SYSTEM_CLIENT;
  }
}

int32 ViewAccessibility::MSAAState(AccessibilityTypes::State state) {
  int32 msaa_state = 0;
  if (state & AccessibilityTypes::STATE_CHECKED)
    msaa_state |= STATE_SYSTEM_CHECKED;
  if (state & AccessibilityTypes::STATE_HASPOPUP)
    msaa_state |= STATE_SYSTEM_HASPOPUP;
  if (state & AccessibilityTypes::STATE_LINKED)
    msaa_state |= STATE_SYSTEM_LINKED;
  if (state & AccessibilityTypes::STATE_PROTECTED)
    msaa_state |= STATE_SYSTEM_PROTECTED;
  if (state & AccessibilityTypes::STATE_READONLY)
    msaa_state |= STATE_SYSTEM_READONLY;
  return msaa_state;
}

HRESULT ViewAccessibility::GetNativeIAccessibleInterface(
    views::NativeViewHost* native_host, IDispatch** disp_child) {
  if (!native_host || !disp_child) {
    return E_INVALIDARG;
  }

  HWND native_view_window =
      static_cast<HWND>(GetProp(native_host->native_view(),
                                kViewsNativeHostPropForAccessibility));
  if (!IsWindow(native_view_window)) {
    native_view_window = native_host->native_view();
  }

  return GetNativeIAccessibleInterface(native_view_window, disp_child);
}

HRESULT ViewAccessibility::GetNativeIAccessibleInterface(
    HWND native_view_window , IDispatch** disp_child) {
  if (IsWindow(native_view_window)) {
    LRESULT ret = SendMessage(native_view_window, WM_GETOBJECT, 0,
                              OBJID_CLIENT);
    return ObjectFromLresult(ret, IID_IDispatch, 0,
                             reinterpret_cast<void**>(disp_child));
  }

  return E_FAIL;
}