Index: ui/accessibility/platform/ax_platform_node_win.cc |
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc |
index 4eeaad701ce32f2f09639a2e3a26c22ac61e14f2..25c8c227dd921ef147e58f955fb22fa390cf08e4 100644 |
--- a/ui/accessibility/platform/ax_platform_node_win.cc |
+++ b/ui/accessibility/platform/ax_platform_node_win.cc |
@@ -135,6 +135,10 @@ |
if (!target->delegate_) \ |
return E_INVALIDARG; |
+const WCHAR* const IA2_RELATION_DETAILS = L"details"; |
+const WCHAR* const IA2_RELATION_DETAILS_FOR = L"detailsFor"; |
+const WCHAR* const IA2_RELATION_ERROR_MESSAGE = L"errorMessage"; |
+ |
namespace ui { |
namespace { |
@@ -167,6 +171,114 @@ base::ObserverList<IAccessible2UsageObserver>& |
return g_iaccessible2_usage_observer_list.Get(); |
} |
+AXPlatformNodeRelationWin::AXPlatformNodeRelationWin() { |
+ ui::win::CreateATLModuleIfNeeded(); |
+} |
+ |
+AXPlatformNodeRelationWin::~AXPlatformNodeRelationWin() {} |
+ |
+void AXPlatformNodeRelationWin::Initialize(AXPlatformNodeWin* owner, |
+ const base::string16& type) { |
+ owner_ = owner; |
+ type_ = type; |
+} |
+ |
+void AXPlatformNodeRelationWin::AddTarget(int target_id) { |
+ target_ids_.push_back(target_id); |
+} |
+ |
+void AXPlatformNodeRelationWin::RemoveTarget(int target_id) { |
+ target_ids_.erase( |
+ std::remove(target_ids_.begin(), target_ids_.end(), target_id), |
+ target_ids_.end()); |
+} |
+ |
+STDMETHODIMP AXPlatformNodeRelationWin::get_relationType(BSTR* relation_type) { |
+ if (!relation_type) |
+ return E_INVALIDARG; |
+ |
+ if (!owner_->delegate_) |
+ return E_FAIL; |
+ |
+ *relation_type = SysAllocString(type_.c_str()); |
+ DCHECK(*relation_type); |
+ return S_OK; |
+} |
+ |
+STDMETHODIMP AXPlatformNodeRelationWin::get_nTargets(long* n_targets) { |
+ if (!n_targets) |
+ return E_INVALIDARG; |
+ |
+ if (!owner_->delegate_) |
+ return E_FAIL; |
+ |
+ *n_targets = static_cast<long>(target_ids_.size()); |
+ |
+ for (long i = *n_targets - 1; i >= 0; --i) { |
+ AXPlatformNodeWin* result = static_cast<AXPlatformNodeWin*>( |
+ owner_->delegate_->GetFromNodeID(target_ids_[i])); |
+ if (!result || !result->delegate_) { |
+ *n_targets = 0; |
+ break; |
+ } |
+ } |
+ return S_OK; |
+} |
+ |
+STDMETHODIMP AXPlatformNodeRelationWin::get_target(long target_index, |
+ IUnknown** target) { |
+ if (!target) |
+ return E_INVALIDARG; |
+ |
+ if (!owner_->delegate_) |
+ return E_FAIL; |
+ |
+ if (target_index < 0 || |
+ target_index >= static_cast<long>(target_ids_.size())) { |
+ return E_INVALIDARG; |
+ } |
+ |
+ AXPlatformNodeWin* result = static_cast<AXPlatformNodeWin*>( |
+ owner_->delegate_->GetFromNodeID(target_ids_[target_index])); |
+ if (!result || !result->delegate_) |
+ return E_FAIL; |
+ |
+ result->AddRef(); |
+ *target = static_cast<IAccessible*>(result); |
+ return S_OK; |
+} |
+ |
+STDMETHODIMP AXPlatformNodeRelationWin::get_targets(long max_targets, |
+ IUnknown** targets, |
+ long* n_targets) { |
+ if (!targets || !n_targets) |
+ return E_INVALIDARG; |
+ |
+ if (!owner_->delegate_) |
+ return E_FAIL; |
+ |
+ long count = static_cast<long>(target_ids_.size()); |
+ if (count > max_targets) |
+ count = max_targets; |
+ |
+ *n_targets = count; |
+ if (count == 0) |
+ return S_FALSE; |
+ |
+ for (long i = 0; i < count; ++i) { |
+ HRESULT result = get_target(i, &targets[i]); |
+ if (result != S_OK) |
+ return result; |
+ } |
+ |
+ return S_OK; |
+} |
+ |
+STDMETHODIMP |
+AXPlatformNodeRelationWin::get_localizedRelationType(BSTR* relation_type) { |
+ return E_NOTIMPL; |
+} |
+ |
// |
// AXPlatformNode::Create |
// |
@@ -202,6 +314,165 @@ AXPlatformNodeWin::AXPlatformNodeWin() { |
} |
AXPlatformNodeWin::~AXPlatformNodeWin() { |
+ for (ui::AXPlatformNodeRelationWin* relation : relations_) |
+ relation->Release(); |
+} |
+ |
+void AXPlatformNodeWin::CalculateRelationships() { |
+ ClearOwnRelations(); |
+ AddBidirectionalRelations(IA2_RELATION_CONTROLLER_FOR, |
+ IA2_RELATION_CONTROLLED_BY, |
+ ui::AX_ATTR_CONTROLS_IDS); |
+ AddBidirectionalRelations(IA2_RELATION_DESCRIBED_BY, |
+ IA2_RELATION_DESCRIPTION_FOR, |
+ ui::AX_ATTR_DESCRIBEDBY_IDS); |
+ AddBidirectionalRelations(IA2_RELATION_FLOWS_TO, IA2_RELATION_FLOWS_FROM, |
+ ui::AX_ATTR_FLOWTO_IDS); |
+ AddBidirectionalRelations(IA2_RELATION_LABELLED_BY, IA2_RELATION_LABEL_FOR, |
+ ui::AX_ATTR_LABELLEDBY_IDS); |
+ |
+ int32_t details_id; |
+ if (GetIntAttribute(ui::AX_ATTR_DETAILS_ID, &details_id)) { |
+ std::vector<int32_t> details_ids; |
+ details_ids.push_back(details_id); |
+ AddBidirectionalRelations(IA2_RELATION_DETAILS, IA2_RELATION_DETAILS_FOR, |
+ details_ids); |
+ } |
+ |
+ int member_of_id; |
+ if (GetIntAttribute(ui::AX_ATTR_MEMBER_OF_ID, &member_of_id)) |
+ AddRelation(IA2_RELATION_MEMBER_OF, member_of_id); |
+ |
+ int error_message_id; |
+ if (GetIntAttribute(ui::AX_ATTR_ERRORMESSAGE_ID, &error_message_id)) |
+ AddRelation(IA2_RELATION_ERROR_MESSAGE, error_message_id); |
+} |
+ |
+void AXPlatformNodeWin::AddRelation(const base::string16& relation_type, |
+ int target_id) { |
+ // Reflexive relations don't need to be exposed through IA2. |
+ if (target_id == GetData().id) |
+ return; |
+ |
+ CComObject<ui::AXPlatformNodeRelationWin>* relation; |
+ HRESULT hr = |
+ CComObject<ui::AXPlatformNodeRelationWin>::CreateInstance(&relation); |
+ DCHECK(SUCCEEDED(hr)); |
+ relation->AddRef(); |
+ relation->Initialize(this, relation_type); |
+ relation->AddTarget(target_id); |
+ relations_.push_back(relation); |
+} |
+ |
+void AXPlatformNodeWin::AddBidirectionalRelations( |
+ const base::string16& relation_type, |
+ const base::string16& reverse_relation_type, |
+ ui::AXIntListAttribute attribute) { |
+ if (!HasIntListAttribute(attribute)) |
+ return; |
+ |
+ const std::vector<int32_t>& target_ids = GetIntListAttribute(attribute); |
+ AddBidirectionalRelations(relation_type, reverse_relation_type, target_ids); |
+} |
+ |
+void AXPlatformNodeWin::AddBidirectionalRelations( |
+ const base::string16& relation_type, |
+ const base::string16& reverse_relation_type, |
+ const std::vector<int32_t>& target_ids) { |
+ // Reflexive relations don't need to be exposed through IA2. |
+ std::vector<int32_t> filtered_target_ids; |
+ int32_t current_id = GetData().id; |
+ std::copy_if(target_ids.begin(), target_ids.end(), |
+ std::back_inserter(filtered_target_ids), |
+ [current_id](int32_t id) { return id != current_id; }); |
+ if (filtered_target_ids.empty()) |
+ return; |
+ |
+ CComObject<ui::AXPlatformNodeRelationWin>* relation; |
+ HRESULT hr = |
+ CComObject<ui::AXPlatformNodeRelationWin>::CreateInstance(&relation); |
+ DCHECK(SUCCEEDED(hr)); |
+ relation->AddRef(); |
+ relation->Initialize(this, relation_type); |
+ |
+ for (int target_id : filtered_target_ids) { |
+ AXPlatformNodeWin* target = static_cast<AXPlatformNodeWin*>( |
+ delegate_->GetFromNodeID(static_cast<int32_t>(target_id))); |
+ |
+ if (!target) |
+ continue; |
+ relation->AddTarget(target_id); |
+ target->AddRelation(reverse_relation_type, GetData().id); |
+ } |
+ |
+ relations_.push_back(relation); |
+} |
+ |
+// Clears all the forward relations from this object to any other object and the |
+// associated reverse relations on the other objects, but leaves any reverse |
+// relations on this object alone. |
+void AXPlatformNodeWin::ClearOwnRelations() { |
+ RemoveBidirectionalRelationsOfType(IA2_RELATION_CONTROLLER_FOR, |
+ IA2_RELATION_CONTROLLED_BY); |
+ RemoveBidirectionalRelationsOfType(IA2_RELATION_DESCRIBED_BY, |
+ IA2_RELATION_DESCRIPTION_FOR); |
+ RemoveBidirectionalRelationsOfType(IA2_RELATION_FLOWS_TO, |
+ IA2_RELATION_FLOWS_FROM); |
+ RemoveBidirectionalRelationsOfType(IA2_RELATION_LABELLED_BY, |
+ IA2_RELATION_LABEL_FOR); |
+ |
+ relations_.erase( |
+ std::remove_if(relations_.begin(), relations_.end(), |
+ [](ui::AXPlatformNodeRelationWin* relation) { |
+ if (relation->get_type() == IA2_RELATION_MEMBER_OF) { |
+ relation->Release(); |
+ return true; |
+ } |
+ return false; |
+ }), |
+ relations_.end()); |
+} |
+ |
+void AXPlatformNodeWin::RemoveBidirectionalRelationsOfType( |
+ const base::string16& relation_type, |
+ const base::string16& reverse_relation_type) { |
+ for (auto iter = relations_.begin(); iter != relations_.end();) { |
+ ui::AXPlatformNodeRelationWin* relation = *iter; |
+ DCHECK(relation); |
+ if (relation->get_type() == relation_type) { |
+ for (int target_id : relation->get_target_ids()) { |
+ AXPlatformNodeWin* target = static_cast<AXPlatformNodeWin*>( |
+ delegate_->GetFromNodeID(static_cast<int32_t>(target_id))); |
+ if (!target) |
+ continue; |
+ DCHECK_NE(target, this); |
+ target->RemoveTargetFromRelation(reverse_relation_type, GetData().id); |
+ } |
+ iter = relations_.erase(iter); |
+ relation->Release(); |
+ } else { |
+ ++iter; |
+ } |
+ } |
+} |
+ |
+void AXPlatformNodeWin::RemoveTargetFromRelation( |
+ const base::string16& relation_type, |
+ int target_id) { |
+ for (auto iter = relations_.begin(); iter != relations_.end();) { |
+ ui::AXPlatformNodeRelationWin* relation = *iter; |
+ DCHECK(relation); |
+ if (relation->get_type() == relation_type) { |
+ // If |target_id| is not present, |RemoveTarget| will do nothing. |
+ relation->RemoveTarget(target_id); |
+ } |
+ if (relation->get_target_ids().empty()) { |
+ iter = relations_.erase(iter); |
+ relation->Release(); |
+ } else { |
+ ++iter; |
+ } |
+ } |
} |
// Static |
@@ -971,31 +1242,50 @@ STDMETHODIMP AXPlatformNodeWin::get_indexInParent(LONG* index_in_parent) { |
return S_OK; |
} |
-// |
-// IAccessible2 methods not implemented. |
-// |
- |
-STDMETHODIMP AXPlatformNodeWin::get_attribute(BSTR name, VARIANT* attribute) { |
- return E_NOTIMPL; |
-} |
- |
-STDMETHODIMP AXPlatformNodeWin::get_extendedRole(BSTR* extended_role) { |
- WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_EXTENDED_ROLE); |
- return E_NOTIMPL; |
-} |
- |
STDMETHODIMP AXPlatformNodeWin::get_nRelations(LONG* n_relations) { |
- return E_NOTIMPL; |
+ COM_OBJECT_VALIDATE_1_ARG(n_relations); |
+ *n_relations = static_cast<LONG>(relations_.size()); |
+ return S_OK; |
} |
STDMETHODIMP AXPlatformNodeWin::get_relation(LONG relation_index, |
IAccessibleRelation** relation) { |
- return E_NOTIMPL; |
+ COM_OBJECT_VALIDATE_1_ARG(relation); |
+ if (relation_index < 0 || |
+ relation_index >= static_cast<long>(relations_.size())) { |
+ return E_INVALIDARG; |
+ } |
+ |
+ relations_[relation_index]->AddRef(); |
+ *relation = relations_[relation_index]; |
+ return S_OK; |
} |
STDMETHODIMP AXPlatformNodeWin::get_relations(LONG max_relations, |
IAccessibleRelation** relations, |
LONG* n_relations) { |
+ COM_OBJECT_VALIDATE_2_ARGS(relations, n_relations); |
+ long count = static_cast<long>(relations_.size()); |
+ *n_relations = count; |
+ if (count == 0) |
+ return S_FALSE; |
+ |
+ for (long i = 0; i < count; ++i) { |
+ relations_[i]->AddRef(); |
+ relations[i] = relations_[i]; |
+ } |
+ |
+ return S_OK; |
+} |
+ |
+// |
+// IAccessible2 methods not implemented. |
+// |
+ |
+STDMETHODIMP AXPlatformNodeWin::get_attribute(BSTR name, VARIANT* attribute) { |
+ return E_NOTIMPL; |
+} |
+STDMETHODIMP AXPlatformNodeWin::get_extendedRole(BSTR* extended_role) { |
return E_NOTIMPL; |
} |