Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(162)

Side by Side Diff: content/browser/accessibility/browser_accessibility_win.cc

Issue 1377733002: Fixes for contenteditable caret and selection handling in Windows. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed some more corner cases, updated tests to make them more resilient and added comments to the c… Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/accessibility/browser_accessibility_win.h" 5 #include "content/browser/accessibility/browser_accessibility_win.h"
6 6
7 #include <UIAutomationClient.h> 7 #include <UIAutomationClient.h>
8 #include <UIAutomationCoreApi.h> 8 #include <UIAutomationCoreApi.h>
9 9
10 #include <algorithm> 10 #include <algorithm>
(...skipping 2046 matching lines...) Expand 10 before | Expand all | Expand 10 after
2057 STDMETHODIMP BrowserAccessibilityWin::get_nSelections(LONG* n_selections) { 2057 STDMETHODIMP BrowserAccessibilityWin::get_nSelections(LONG* n_selections) {
2058 if (!instance_active()) 2058 if (!instance_active())
2059 return E_FAIL; 2059 return E_FAIL;
2060 2060
2061 if (!n_selections) 2061 if (!n_selections)
2062 return E_INVALIDARG; 2062 return E_INVALIDARG;
2063 2063
2064 *n_selections = 0; 2064 *n_selections = 0;
2065 int selection_start, selection_end; 2065 int selection_start, selection_end;
2066 GetSelectionOffsets(&selection_start, &selection_end); 2066 GetSelectionOffsets(&selection_start, &selection_end);
2067 if (selection_start >= 0 && selection_end >= 0 && 2067
2068 // A selection exists either: when both selection endpoints are inside this
2069 // object but their values are not equal, or when one endpoint is inside and
2070 // the other outside this object.
2071 if ((selection_start >= 0 || selection_end >= 0) &&
2068 selection_start != selection_end) 2072 selection_start != selection_end)
2069 *n_selections = 1; 2073 *n_selections = 1;
2070 2074
2071 return S_OK; 2075 return S_OK;
2072 } 2076 }
2073 2077
2074 STDMETHODIMP BrowserAccessibilityWin::get_selection(LONG selection_index, 2078 STDMETHODIMP BrowserAccessibilityWin::get_selection(LONG selection_index,
2075 LONG* start_offset, 2079 LONG* start_offset,
2076 LONG* end_offset) { 2080 LONG* end_offset) {
2077 if (!instance_active()) 2081 if (!instance_active())
(...skipping 1563 matching lines...) Expand 10 before | Expand all | Expand 10 after
3641 int value; 3645 int value;
3642 if (GetIntAttribute(attribute, &value)) { 3646 if (GetIntAttribute(attribute, &value)) {
3643 win_attributes_->ia2_attributes.push_back( 3647 win_attributes_->ia2_attributes.push_back(
3644 base::ASCIIToUTF16(ia2_attr) + L":" + 3648 base::ASCIIToUTF16(ia2_attr) + L":" +
3645 base::IntToString16(value)); 3649 base::IntToString16(value));
3646 } 3650 }
3647 } 3651 }
3648 3652
3649 int32 BrowserAccessibilityWin::GetHyperlinkIndexFromChild( 3653 int32 BrowserAccessibilityWin::GetHyperlinkIndexFromChild(
3650 const BrowserAccessibilityWin& child) const { 3654 const BrowserAccessibilityWin& child) const {
3655 if (hyperlinks().empty())
3656 return -1;
3657
3651 auto iterator = std::find( 3658 auto iterator = std::find(
3652 hyperlinks().begin(), hyperlinks().end(), child.GetId()); 3659 hyperlinks().begin(), hyperlinks().end(), child.GetId());
3653 if (iterator == hyperlinks().end()) 3660 if (iterator == hyperlinks().end())
3654 return -1; 3661 return -1;
3655 3662
3656 return static_cast<int32>(iterator - hyperlinks().begin()); 3663 return static_cast<int32>(iterator - hyperlinks().begin());
3657 } 3664 }
3658 3665
3659 int32 BrowserAccessibilityWin::GetHypertextOffsetFromHyperlinkIndex( 3666 int32 BrowserAccessibilityWin::GetHypertextOffsetFromHyperlinkIndex(
3660 int32 hyperlink_index) const { 3667 int32 hyperlink_index) const {
3661 auto& offsets_map = hyperlink_offset_to_index(); 3668 for (auto& offset_index : hyperlink_offset_to_index()) {
3662 for (auto& offset_index : offsets_map) {
3663 if (offset_index.second == hyperlink_index) 3669 if (offset_index.second == hyperlink_index)
3664 return offset_index.first; 3670 return offset_index.first;
3665 } 3671 }
3666 3672
3667 return -1; 3673 return -1;
3668 } 3674 }
3669 3675
3670 int32 BrowserAccessibilityWin::GetHypertextOffsetFromChild( 3676 int32 BrowserAccessibilityWin::GetHypertextOffsetFromChild(
3671 const BrowserAccessibilityWin& child) const { 3677 const BrowserAccessibilityWin& child) const {
3672 int32 hyperlink_index = GetHyperlinkIndexFromChild(child); 3678 int32 hyperlink_index = GetHyperlinkIndexFromChild(child);
(...skipping 10 matching lines...) Expand all
3683 while (parent_object && parent_object != this) { 3689 while (parent_object && parent_object != this) {
3684 current_object = parent_object; 3690 current_object = parent_object;
3685 parent_object = current_object->GetParent()->ToBrowserAccessibilityWin(); 3691 parent_object = current_object->GetParent()->ToBrowserAccessibilityWin();
3686 } 3692 }
3687 if (!parent_object) 3693 if (!parent_object)
3688 return -1; 3694 return -1;
3689 3695
3690 return parent_object->GetHypertextOffsetFromChild(*current_object); 3696 return parent_object->GetHypertextOffsetFromChild(*current_object);
3691 } 3697 }
3692 3698
3699 int BrowserAccessibilityWin::GetHypertextOffsetFromEndpoint(
3700 const BrowserAccessibilityWin& endpoint_object, int endpoint_offset) const {
3701 // There are three cases:
3702 // 1. Either the selection endpoint is inside this object or is an ancestor of
3703 // of this object. endpoint_offset should be returned.
3704 // 2. The selection endpoint is a pure descendant of this object. The offset
3705 // of the embedded object character corresponding to the subtree in which
3706 // the endpoint is located should be returned.
3707 // 3. The selection endpoint is in a completely different part of the tree.
3708 // Either 0 or text_length should be returned depending on the direction that
3709 // one needs to travel to find the endpoint.
3710
3711
3712 // Case 1.
3713
3714 // IsDescendantOf includes the case when endpoint_object == this.
3715 if (IsDescendantOf(&endpoint_object) ||
3716 // Handle the case when the endpoint is a direct text-only child.
3717 // The offset should still be valid on the parent.
3718 (endpoint_object.IsTextOnlyObject() &&
3719 endpoint_object.GetParent() == this)) {
3720 return endpoint_offset;
3721 }
3722
dmazzoni 2015/09/30 20:09:56 Please remove the double blank lines within a func
3723
3724 const BrowserAccessibility* common_parent = this;
3725 while (common_parent && !endpoint_object.IsDescendantOf(common_parent)) {
3726 common_parent = common_parent->GetParent();
3727 }
3728 if (!common_parent)
3729 return -1;
3730
3731 // TODO(nektar): Add a const version of ToBrowserAccessibilityWin.
dmazzoni 2015/09/30 20:09:56 Please just do this as part of this changelist
3732 auto common_parent_win = static_cast<const BrowserAccessibilityWin*>(
3733 common_parent);
3734
3735 // Text only objects must have a parent.
3736 DCHECK(!IsTextOnlyObject() || GetParent());
3737 DCHECK(!endpoint_object.IsTextOnlyObject() || endpoint_object.GetParent());
3738 // Text only objects that are direct descendants should behave as if they
3739 // are part of their parent when computing hyperlink offsets.
3740 const BrowserAccessibilityWin* non_text_this = IsTextOnlyObject() ?
dmazzoni 2015/09/30 20:09:56 how about nearest_non_text_ancestor
3741 GetParent()->ToBrowserAccessibilityWin() : this;
3742 const BrowserAccessibilityWin& non_text_endpoint =
3743 endpoint_object.IsTextOnlyObject() ?
3744 *(endpoint_object.GetParent()->ToBrowserAccessibilityWin()) :
dmazzoni 2015/09/30 20:09:56 I'd indent this and the next line 4 more spaces be
3745 endpoint_object;
3746
3747
3748 // Case 2.
3749
3750 // We already checked in case 1 if our endpoint is inside this object.
3751 // We can safely assume that it is a descendant or in a completely different
3752 // part of the tree.
3753 if (common_parent_win == non_text_this)
3754 return non_text_this->GetHypertextOffsetFromDescendant(non_text_endpoint);
3755
3756
3757 // Case 3.
3758
3759 // We can safely assume that the endpoint is in another part of the tree or
3760 // at common parent, and that this object is a descendant of common parent.
3761 int32 current_offset = static_cast<int>(
dmazzoni 2015/09/30 20:09:56 nit: <int32> to match the type you're assigning to
3762 common_parent_win->GetHypertextOffsetFromDescendant(*non_text_this));
3763 DCHECK_GE(current_offset, 0);
3764 if (common_parent_win != &non_text_endpoint) {
3765 endpoint_offset = static_cast<int>(
3766 common_parent_win->GetHypertextOffsetFromDescendant(non_text_endpoint));
3767 DCHECK_GE(endpoint_offset, 0);
3768 }
3769
3770 if (endpoint_offset < current_offset)
3771 return 0;
3772 if (endpoint_offset > current_offset)
3773 return TextForIAccessibleText().length();
3774
3775 NOTREACHED();
3776 return -1;
3777 }
3778
3693 int BrowserAccessibilityWin::GetSelectionAnchor() const { 3779 int BrowserAccessibilityWin::GetSelectionAnchor() const {
3694 BrowserAccessibility* root = manager()->GetRoot(); 3780 BrowserAccessibility* root = manager()->GetRoot();
3695 int32 anchor_id; 3781 int32 anchor_id;
3696 if (!root || !root->GetIntAttribute(ui::AX_ATTR_ANCHOR_OBJECT_ID, &anchor_id)) 3782 if (!root || !root->GetIntAttribute(ui::AX_ATTR_ANCHOR_OBJECT_ID, &anchor_id))
3697 return -1; 3783 return -1;
3698 3784
3699 BrowserAccessibilityWin* anchor_object = manager()->GetFromID( 3785 BrowserAccessibilityWin* anchor_object = manager()->GetFromID(
3700 anchor_id)->ToBrowserAccessibilityWin(); 3786 anchor_id)->ToBrowserAccessibilityWin();
3701 if (!anchor_object) 3787 if (!anchor_object)
3702 return -1; 3788 return -1;
3703 3789
3704 // Includes the case when anchor_object == this. 3790 int anchor_offset;
3705 if (IsDescendantOf(anchor_object) || 3791 if (!root->GetIntAttribute(ui::AX_ATTR_ANCHOR_OFFSET, &anchor_offset))
3706 // Text only objects that are direct descendants should behave as if they 3792 return -1;
3707 // are part of this object when computing hypertext.
3708 (anchor_object->GetParent() == this &&
3709 anchor_object->IsTextOnlyObject())) {
3710 int anchor_offset;
3711 if (!root->GetIntAttribute(ui::AX_ATTR_ANCHOR_OFFSET, &anchor_offset))
3712 return -1;
3713 3793
3714 return anchor_offset; 3794 return GetHypertextOffsetFromEndpoint(*anchor_object, anchor_offset);
3715 }
3716
3717 if (anchor_object->IsDescendantOf(this))
3718 return GetHypertextOffsetFromDescendant(*anchor_object);
3719
3720 return -1;
3721 } 3795 }
3722 3796
3723 int BrowserAccessibilityWin::GetSelectionFocus() const { 3797 int BrowserAccessibilityWin::GetSelectionFocus() const {
3724 BrowserAccessibility* root = manager()->GetRoot(); 3798 BrowserAccessibility* root = manager()->GetRoot();
3725 int32 focus_id; 3799 int32 focus_id;
3726 if (!root || !root->GetIntAttribute(ui::AX_ATTR_FOCUS_OBJECT_ID, &focus_id)) 3800 if (!root || !root->GetIntAttribute(ui::AX_ATTR_FOCUS_OBJECT_ID, &focus_id))
3727 return -1; 3801 return -1;
3728 3802
3729 BrowserAccessibilityWin* focus_object = manager()->GetFromID( 3803 BrowserAccessibilityWin* focus_object = manager()->GetFromID(
3730 focus_id)->ToBrowserAccessibilityWin(); 3804 focus_id)->ToBrowserAccessibilityWin();
3731 if (!focus_object) 3805 if (!focus_object)
3732 return -1; 3806 return -1;
3733 3807
3734 // Includes the case when focus_object == this. 3808 int focus_offset;
3735 if (IsDescendantOf(focus_object) || 3809 if (!root->GetIntAttribute(ui::AX_ATTR_FOCUS_OFFSET, &focus_offset))
3736 // Text only objects that are direct descendants should behave as if they 3810 return -1;
3737 // are part of this object when computing hypertext.
3738 (focus_object->GetParent() == this && focus_object->IsTextOnlyObject())) {
3739 int focus_offset;
3740 if (!root->GetIntAttribute(ui::AX_ATTR_FOCUS_OFFSET, &focus_offset))
3741 return -1;
3742 3811
3743 return focus_offset; 3812 return GetHypertextOffsetFromEndpoint(*focus_object, focus_offset);
3744 }
3745
3746 if (focus_object->IsDescendantOf(this))
3747 return GetHypertextOffsetFromDescendant(*focus_object);
3748
3749 return -1;
3750 } 3813 }
3751 3814
3752 void BrowserAccessibilityWin::GetSelectionOffsets( 3815 void BrowserAccessibilityWin::GetSelectionOffsets(
3753 int* selection_start, int* selection_end) const { 3816 int* selection_start, int* selection_end) const {
3754 DCHECK(selection_start && selection_end); 3817 DCHECK(selection_start && selection_end);
3755 3818
3756 if (IsEditableText() && !HasState(ui::AX_STATE_RICHLY_EDITABLE) && 3819 if (IsEditableText() && !HasState(ui::AX_STATE_RICHLY_EDITABLE) &&
3757 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, selection_start) && 3820 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, selection_start) &&
3758 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, selection_end)) { 3821 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, selection_end)) {
3759 return; 3822 return;
3760 } 3823 }
3761 3824
3762 *selection_start = GetSelectionAnchor(); 3825 *selection_start = GetSelectionAnchor();
3763 *selection_end = GetSelectionFocus(); 3826 *selection_end = GetSelectionFocus();
3764 if (*selection_start < 0 || *selection_end < 0) 3827 if (*selection_start < 0 || *selection_end < 0)
3765 return; 3828 return;
3766 3829
3830 // If the selection is collapsed, then return the caret position only if the
3831 // caret is active on this object.
3832 // The focus object indicates the caret position, but we will use the anchor
3833 // for consistency with Firefox.
3834 // TODO(nektar): Investigate why Firefox uses the anchor and not the focus.
3835 if (*selection_start == *selection_end) {
3836 BrowserAccessibility* root = manager()->GetRoot();
3837 int32 anchor_id;
3838 if (!root || !root->GetIntAttribute(
3839 ui::AX_ATTR_ANCHOR_OBJECT_ID, &anchor_id)) {
3840 return;
3841 }
3842
3843 BrowserAccessibilityWin* anchor_object = manager()->GetFromID(
3844 anchor_id)->ToBrowserAccessibilityWin();
3845 if (!anchor_object)
3846 return;
3847
3848 if (!anchor_object->IsDescendantOf(this) &&
3849 (IsTextOnlyObject() && GetParent() != anchor_object)) {
3850 *selection_start = -1;
3851 *selection_end = -1;
3852 return;
3853 }
3854 }
3855
3767 if (*selection_end < *selection_start) 3856 if (*selection_end < *selection_start)
3768 std::swap(*selection_start, *selection_end); 3857 std::swap(*selection_start, *selection_end);
3769 3858
3770 // IA2 Spec says that the end of the selection should be after the last 3859 // The IA2 Spec says that if a selection is present and if the end of the
3771 // embedded object character that is part of the selection, if there is one. 3860 // selection falls on an embedded object character, it should be incremented
3772 if (hyperlink_offset_to_index().find(*selection_end) != 3861 // by one so that it points after the embedded object character.
3773 hyperlink_offset_to_index().end()) { 3862 LONG hyperlink_index;
3863 auto current_object = const_cast<BrowserAccessibilityWin*>(this);
3864 HRESULT hr = current_object->get_hyperlinkIndex(
3865 *selection_end, &hyperlink_index);
3866 if (hr != S_OK)
3867 return;
3868
3869 base::win::ScopedComPtr<IAccessibleHyperlink> hyperlink;
3870 hr = current_object->get_hyperlink(
3871 hyperlink_index, hyperlink.Receive());
3872 DCHECK(SUCCEEDED(hr));
3873 base::win::ScopedComPtr<IAccessibleText> ax_hyperlink;
3874 hr = hyperlink.QueryInterface(ax_hyperlink.Receive());
3875 DCHECK(SUCCEEDED(hr));
3876 LONG nSelections = -1;
3877 hr = ax_hyperlink->get_nSelections(&nSelections);
3878 DCHECK(SUCCEEDED(hr));
3879
3880 if (nSelections > 0)
3774 ++(*selection_end); 3881 ++(*selection_end);
3775 }
3776 } 3882 }
3777 3883
3778 base::string16 BrowserAccessibilityWin::GetNameRecursive() const { 3884 base::string16 BrowserAccessibilityWin::GetNameRecursive() const {
3779 if (!name().empty()) { 3885 if (!name().empty()) {
3780 return name(); 3886 return name();
3781 } 3887 }
3782 3888
3783 base::string16 result; 3889 base::string16 result;
3784 for (uint32 i = 0; i < PlatformChildCount(); ++i) { 3890 for (uint32 i = 0; i < PlatformChildCount(); ++i) {
3785 result += PlatformGetChild(i)->ToBrowserAccessibilityWin()-> 3891 result += PlatformGetChild(i)->ToBrowserAccessibilityWin()->
3786 GetNameRecursive(); 3892 GetNameRecursive();
3787 } 3893 }
3788 return result; 3894 return result;
3789 } 3895 }
3790 3896
3791 base::string16 BrowserAccessibilityWin::GetValueText() { 3897 base::string16 BrowserAccessibilityWin::GetValueText() {
3792 float fval; 3898 float fval;
3793 base::string16 value = this->value(); 3899 base::string16 value = this->value();
3794 3900
3795 if (value.empty() && 3901 if (value.empty() &&
3796 GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, &fval)) { 3902 GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, &fval)) {
3797 value = base::UTF8ToUTF16(base::DoubleToString(fval)); 3903 value = base::UTF8ToUTF16(base::DoubleToString(fval));
3798 } 3904 }
3799 return value; 3905 return value;
3800 } 3906 }
3801 3907
3802 base::string16 BrowserAccessibilityWin::TextForIAccessibleText() { 3908 base::string16 BrowserAccessibilityWin::TextForIAccessibleText() const {
3803 switch (GetRole()) { 3909 switch (GetRole()) {
3804 case ui::AX_ROLE_TEXT_FIELD: 3910 case ui::AX_ROLE_TEXT_FIELD:
3805 case ui::AX_ROLE_MENU_LIST_OPTION: 3911 case ui::AX_ROLE_MENU_LIST_OPTION:
3806 return value(); 3912 return value();
3807 default: 3913 default:
3808 return hypertext(); 3914 return hypertext();
3809 } 3915 }
3810 } 3916 }
3811 3917
3812 bool BrowserAccessibilityWin::IsSameHypertextCharacter(size_t old_char_index, 3918 bool BrowserAccessibilityWin::IsSameHypertextCharacter(size_t old_char_index,
(...skipping 686 matching lines...) Expand 10 before | Expand all | Expand 10 after
4499 ia2_role = ia_role; 4605 ia2_role = ia_role;
4500 4606
4501 win_attributes_->ia_role = ia_role; 4607 win_attributes_->ia_role = ia_role;
4502 win_attributes_->ia_state = ia_state; 4608 win_attributes_->ia_state = ia_state;
4503 win_attributes_->role_name = role_name; 4609 win_attributes_->role_name = role_name;
4504 win_attributes_->ia2_role = ia2_role; 4610 win_attributes_->ia2_role = ia2_role;
4505 win_attributes_->ia2_state = ia2_state; 4611 win_attributes_->ia2_state = ia2_state;
4506 } 4612 }
4507 4613
4508 } // namespace content 4614 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698