summaryrefslogtreecommitdiffstats
path: root/accessible/windows/uia/uiaRawElmProvider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/windows/uia/uiaRawElmProvider.cpp')
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.cpp576
1 files changed, 558 insertions, 18 deletions
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp
index 881ed22277..c022e40cef 100644
--- a/accessible/windows/uia/uiaRawElmProvider.cpp
+++ b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -6,39 +6,174 @@
#include "uiaRawElmProvider.h"
+#include <comdef.h>
+#include <uiautomationcoreapi.h>
+
#include "AccAttributes.h"
#include "AccessibleWrap.h"
#include "ARIAMap.h"
#include "LocalAccessible-inl.h"
#include "mozilla/a11y/RemoteAccessible.h"
+#include "mozilla/StaticPrefs_accessibility.h"
#include "MsaaAccessible.h"
+#include "MsaaRootAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
#include "nsTextEquivUtils.h"
+#include "RootAccessible.h"
using namespace mozilla;
using namespace mozilla::a11y;
+// Helper functions
+
+static ToggleState ToToggleState(uint64_t aState) {
+ if (aState & states::MIXED) {
+ return ToggleState_Indeterminate;
+ }
+ if (aState & (states::CHECKED | states::PRESSED)) {
+ return ToggleState_On;
+ }
+ return ToggleState_Off;
+}
+
+static ExpandCollapseState ToExpandCollapseState(uint64_t aState) {
+ if (aState & states::EXPANDED) {
+ return ExpandCollapseState_Expanded;
+ }
+ // If aria-haspopup is specified without aria-expanded, we should still expose
+ // collapsed, since aria-haspopup infers that it can be expanded. The
+ // alternative is ExpandCollapseState_LeafNode, but that means that the
+ // element can't be expanded nor collapsed.
+ if (aState & (states::COLLAPSED | states::HASPOPUP)) {
+ return ExpandCollapseState_Collapsed;
+ }
+ return ExpandCollapseState_LeafNode;
+}
+
////////////////////////////////////////////////////////////////////////////////
// uiaRawElmProvider
////////////////////////////////////////////////////////////////////////////////
-Accessible* uiaRawElmProvider::Acc() {
- return static_cast<MsaaAccessible*>(this)->Acc();
+Accessible* uiaRawElmProvider::Acc() const {
+ return static_cast<const MsaaAccessible*>(this)->Acc();
}
-// IUnknown
-
-// Because uiaRawElmProvider inherits multiple COM interfaces (and thus multiple
-// IUnknowns), we need to explicitly implement AddRef and Release to make
-// our QueryInterface implementation (IMPL_IUNKNOWN2) happy.
-ULONG STDMETHODCALLTYPE uiaRawElmProvider::AddRef() {
- return static_cast<MsaaAccessible*>(this)->AddRef();
+/* static */
+void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc,
+ uint32_t aGeckoEvent) {
+ if (!StaticPrefs::accessibility_uia_enable()) {
+ return;
+ }
+ auto* uia = MsaaAccessible::GetFrom(aAcc);
+ if (!uia) {
+ return;
+ }
+ PROPERTYID property = 0;
+ _variant_t newVal;
+ bool gotNewVal = false;
+ // For control pattern properties, we can't use GetPropertyValue. In those
+ // cases, we must set newVal appropriately and set gotNewVal to true.
+ switch (aGeckoEvent) {
+ case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
+ property = UIA_FullDescriptionPropertyId;
+ break;
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ ::UiaRaiseAutomationEvent(uia, UIA_AutomationFocusChangedEventId);
+ return;
+ case nsIAccessibleEvent::EVENT_NAME_CHANGE:
+ property = UIA_NamePropertyId;
+ break;
+ case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
+ property = UIA_ValueValuePropertyId;
+ newVal.vt = VT_BSTR;
+ uia->get_Value(&newVal.bstrVal);
+ gotNewVal = true;
+ break;
+ }
+ if (property && ::UiaClientsAreListening()) {
+ // We can't get the old value. Thankfully, clients don't seem to need it.
+ _variant_t oldVal;
+ if (!gotNewVal) {
+ // This isn't a pattern property, so we can use GetPropertyValue.
+ uia->GetPropertyValue(property, &newVal);
+ }
+ ::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal);
+ }
}
-ULONG STDMETHODCALLTYPE uiaRawElmProvider::Release() {
- return static_cast<MsaaAccessible*>(this)->Release();
+/* static */
+void uiaRawElmProvider::RaiseUiaEventForStateChange(Accessible* aAcc,
+ uint64_t aState,
+ bool aEnabled) {
+ if (!StaticPrefs::accessibility_uia_enable()) {
+ return;
+ }
+ auto* uia = MsaaAccessible::GetFrom(aAcc);
+ if (!uia) {
+ return;
+ }
+ PROPERTYID property = 0;
+ _variant_t newVal;
+ switch (aState) {
+ case states::CHECKED:
+ case states::MIXED:
+ case states::PRESSED:
+ property = UIA_ToggleToggleStatePropertyId;
+ newVal.vt = VT_I4;
+ newVal.lVal = ToToggleState(aEnabled ? aState : 0);
+ break;
+ case states::COLLAPSED:
+ case states::EXPANDED:
+ case states::HASPOPUP:
+ property = UIA_ExpandCollapseExpandCollapseStatePropertyId;
+ newVal.vt = VT_I4;
+ newVal.lVal = ToExpandCollapseState(aEnabled ? aState : 0);
+ break;
+ case states::UNAVAILABLE:
+ property = UIA_IsEnabledPropertyId;
+ newVal.vt = VT_BOOL;
+ newVal.boolVal = aEnabled ? VARIANT_FALSE : VARIANT_TRUE;
+ break;
+ default:
+ return;
+ }
+ MOZ_ASSERT(property);
+ if (::UiaClientsAreListening()) {
+ // We can't get the old value. Thankfully, clients don't seem to need it.
+ _variant_t oldVal;
+ ::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal);
+ }
}
-IMPL_IUNKNOWN2(uiaRawElmProvider, IAccessibleEx, IRawElementProviderSimple)
+// IUnknown
+
+STDMETHODIMP
+uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) {
+ *aInterface = nullptr;
+ if (aIid == IID_IAccessibleEx) {
+ *aInterface = static_cast<IAccessibleEx*>(this);
+ } else if (aIid == IID_IRawElementProviderSimple) {
+ *aInterface = static_cast<IRawElementProviderSimple*>(this);
+ } else if (aIid == IID_IRawElementProviderFragment) {
+ *aInterface = static_cast<IRawElementProviderFragment*>(this);
+ } else if (aIid == IID_IExpandCollapseProvider) {
+ *aInterface = static_cast<IExpandCollapseProvider*>(this);
+ } else if (aIid == IID_IInvokeProvider) {
+ *aInterface = static_cast<IInvokeProvider*>(this);
+ } else if (aIid == IID_IScrollItemProvider) {
+ *aInterface = static_cast<IScrollItemProvider*>(this);
+ } else if (aIid == IID_IToggleProvider) {
+ *aInterface = static_cast<IToggleProvider*>(this);
+ } else if (aIid == IID_IValueProvider) {
+ *aInterface = static_cast<IValueProvider*>(this);
+ } else {
+ return E_NOINTERFACE;
+ }
+ MOZ_ASSERT(*aInterface);
+ static_cast<MsaaAccessible*>(this)->AddRef();
+ return S_OK;
+}
////////////////////////////////////////////////////////////////////////////////
// IAccessibleEx
@@ -113,8 +248,9 @@ uiaRawElmProvider::get_ProviderOptions(
__RPC__out enum ProviderOptions* aOptions) {
if (!aOptions) return E_INVALIDARG;
- // This method is not used with IAccessibleEx implementations.
- *aOptions = ProviderOptions_ServerSideProvider;
+ *aOptions = static_cast<enum ProviderOptions>(
+ ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading |
+ ProviderOptions_HasNativeIAccessible);
return S_OK;
}
@@ -122,8 +258,46 @@ STDMETHODIMP
uiaRawElmProvider::GetPatternProvider(
PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) {
if (!aPatternProvider) return E_INVALIDARG;
-
*aPatternProvider = nullptr;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ switch (aPatternId) {
+ case UIA_ExpandCollapsePatternId:
+ if (HasExpandCollapsePattern()) {
+ RefPtr<IExpandCollapseProvider> expand = this;
+ expand.forget(aPatternProvider);
+ }
+ return S_OK;
+ case UIA_InvokePatternId:
+ // Per the UIA documentation, we should only expose the Invoke pattern "if
+ // the same behavior is not exposed through another control pattern
+ // provider".
+ if (acc->ActionCount() > 0 && !HasTogglePattern() &&
+ !HasExpandCollapsePattern()) {
+ RefPtr<IInvokeProvider> invoke = this;
+ invoke.forget(aPatternProvider);
+ }
+ return S_OK;
+ case UIA_ScrollItemPatternId: {
+ RefPtr<IScrollItemProvider> scroll = this;
+ scroll.forget(aPatternProvider);
+ return S_OK;
+ }
+ case UIA_TogglePatternId:
+ if (HasTogglePattern()) {
+ RefPtr<IToggleProvider> toggle = this;
+ toggle.forget(aPatternProvider);
+ }
+ return S_OK;
+ case UIA_ValuePatternId:
+ if (HasValuePattern()) {
+ RefPtr<IValueProvider> value = this;
+ value.forget(aPatternProvider);
+ }
+ return S_OK;
+ }
return S_OK;
}
@@ -230,11 +404,71 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
break;
}
- case UIA_IsControlElementPropertyId:
+ case UIA_AutomationIdPropertyId: {
+ nsAutoString id;
+ acc->DOMNodeID(id);
+ if (!id.IsEmpty()) {
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(id.get());
+ return S_OK;
+ }
+ break;
+ }
+
+ case UIA_ControlTypePropertyId:
+ aPropertyValue->vt = VT_I4;
+ aPropertyValue->lVal = GetControlType();
+ break;
+
+ case UIA_FullDescriptionPropertyId: {
+ nsAutoString desc;
+ acc->Description(desc);
+ if (!desc.IsEmpty()) {
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(desc.get());
+ return S_OK;
+ }
+ break;
+ }
+
+ case UIA_HasKeyboardFocusPropertyId:
+ aPropertyValue->vt = VT_BOOL;
+ aPropertyValue->boolVal = VARIANT_FALSE;
+ if (auto* focusMgr = FocusMgr()) {
+ if (focusMgr->IsFocused(acc)) {
+ aPropertyValue->boolVal = VARIANT_TRUE;
+ }
+ }
+ return S_OK;
+
case UIA_IsContentElementPropertyId:
+ case UIA_IsControlElementPropertyId:
aPropertyValue->vt = VT_BOOL;
aPropertyValue->boolVal = IsControl() ? VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
+
+ case UIA_IsEnabledPropertyId:
+ aPropertyValue->vt = VT_BOOL;
+ aPropertyValue->boolVal =
+ (acc->State() & states::UNAVAILABLE) ? VARIANT_FALSE : VARIANT_TRUE;
+ return S_OK;
+
+ case UIA_IsKeyboardFocusablePropertyId:
+ aPropertyValue->vt = VT_BOOL;
+ aPropertyValue->boolVal =
+ (acc->State() & states::FOCUSABLE) ? VARIANT_TRUE : VARIANT_FALSE;
+ return S_OK;
+
+ case UIA_NamePropertyId: {
+ nsAutoString name;
+ acc->Name(name);
+ if (!name.IsEmpty()) {
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(name.get());
+ return S_OK;
+ }
+ break;
+ }
}
return S_OK;
@@ -244,9 +478,274 @@ STDMETHODIMP
uiaRawElmProvider::get_HostRawElementProvider(
__RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider) {
if (!aRawElmProvider) return E_INVALIDARG;
-
- // This method is not used with IAccessibleEx implementations.
*aRawElmProvider = nullptr;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ if (acc->IsRoot()) {
+ HWND hwnd = MsaaAccessible::GetHWNDFor(acc);
+ return UiaHostProviderFromHwnd(hwnd, aRawElmProvider);
+ }
+ return S_OK;
+}
+
+// IRawElementProviderFragment
+
+STDMETHODIMP
+uiaRawElmProvider::Navigate(
+ enum NavigateDirection aDirection,
+ __RPC__deref_out_opt IRawElementProviderFragment** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ Accessible* target = nullptr;
+ switch (aDirection) {
+ case NavigateDirection_Parent:
+ if (!acc->IsRoot()) {
+ target = acc->Parent();
+ }
+ break;
+ case NavigateDirection_NextSibling:
+ if (!acc->IsRoot()) {
+ target = acc->NextSibling();
+ }
+ break;
+ case NavigateDirection_PreviousSibling:
+ if (!acc->IsRoot()) {
+ target = acc->PrevSibling();
+ }
+ break;
+ case NavigateDirection_FirstChild:
+ if (!nsAccUtils::MustPrune(acc)) {
+ target = acc->FirstChild();
+ }
+ break;
+ case NavigateDirection_LastChild:
+ if (!nsAccUtils::MustPrune(acc)) {
+ target = acc->LastChild();
+ }
+ break;
+ default:
+ return E_INVALIDARG;
+ }
+ RefPtr<IRawElementProviderFragment> fragment =
+ MsaaAccessible::GetFrom(target);
+ fragment.forget(aRetVal);
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_BoundingRectangle(__RPC__out struct UiaRect* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ LayoutDeviceIntRect rect = acc->Bounds();
+ aRetVal->left = rect.X();
+ aRetVal->top = rect.Y();
+ aRetVal->width = rect.Width();
+ aRetVal->height = rect.Height();
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetEmbeddedFragmentRoots(
+ __RPC__deref_out_opt SAFEARRAY** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::SetFocus() {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ acc->TakeFocus();
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_FragmentRoot(
+ __RPC__deref_out_opt IRawElementProviderFragmentRoot** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ LocalAccessible* localAcc = acc->AsLocal();
+ if (!localAcc) {
+ localAcc = acc->AsRemote()->OuterDocOfRemoteBrowser();
+ if (!localAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ }
+ MsaaAccessible* msaa = MsaaAccessible::GetFrom(localAcc->RootAccessible());
+ RefPtr<IRawElementProviderFragmentRoot> fragRoot =
+ static_cast<MsaaRootAccessible*>(msaa);
+ fragRoot.forget(aRetVal);
+ return S_OK;
+}
+
+// IInvokeProvider methods
+
+STDMETHODIMP
+uiaRawElmProvider::Invoke() {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ if (acc->DoAction(0)) {
+ // We don't currently have a way to notify when the action was actually
+ // handled. The UIA documentation says it's okay to fire this immediately if
+ // it "is not possible or practical to wait until the action is complete".
+ ::UiaRaiseAutomationEvent(this, UIA_Invoke_InvokedEventId);
+ }
+ return S_OK;
+}
+
+// IToggleProvider methods
+
+STDMETHODIMP
+uiaRawElmProvider::Toggle() {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ acc->DoAction(0);
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_ToggleState(__RPC__out enum ToggleState* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = ToToggleState(acc->State());
+ return S_OK;
+}
+
+// IExpandCollapseProvider methods
+
+STDMETHODIMP
+uiaRawElmProvider::Expand() {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ if (acc->State() & states::EXPANDED) {
+ return UIA_E_INVALIDOPERATION;
+ }
+ acc->DoAction(0);
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::Collapse() {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ if (acc->State() & states::COLLAPSED) {
+ return UIA_E_INVALIDOPERATION;
+ }
+ acc->DoAction(0);
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_ExpandCollapseState(
+ __RPC__out enum ExpandCollapseState* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = ToExpandCollapseState(acc->State());
+ return S_OK;
+}
+
+// IScrollItemProvider methods
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP uiaRawElmProvider::ScrollIntoView() {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ acc->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+ return S_OK;
+}
+
+// IValueProvider methods
+
+STDMETHODIMP
+uiaRawElmProvider::SetValue(__RPC__in LPCWSTR aVal) {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ HyperTextAccessibleBase* ht = acc->AsHyperTextBase();
+ if (!ht || !acc->IsTextRole()) {
+ return UIA_E_INVALIDOPERATION;
+ }
+ if (acc->State() & (states::READONLY | states::UNAVAILABLE)) {
+ return UIA_E_INVALIDOPERATION;
+ }
+ nsAutoString text(aVal);
+ ht->ReplaceText(text);
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_Value(__RPC__deref_out_opt BSTR* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ nsAutoString value;
+ acc->Value(value);
+ *aRetVal = ::SysAllocStringLen(value.get(), value.Length());
+ if (!*aRetVal) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_IsReadOnly(__RPC__out BOOL* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = acc->State() & states::READONLY;
return S_OK;
}
@@ -314,3 +813,44 @@ bool uiaRawElmProvider::IsControl() {
return true;
}
+
+long uiaRawElmProvider::GetControlType() const {
+ Accessible* acc = Acc();
+ MOZ_ASSERT(acc);
+#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
+ msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
+ nameRule) \
+ case roles::_geckoRole: \
+ return uiaControlType; \
+ break;
+ switch (acc->Role()) {
+#include "RoleMap.h"
+ }
+#undef ROLE
+ MOZ_CRASH("Unknown role.");
+ return 0;
+}
+
+bool uiaRawElmProvider::HasTogglePattern() {
+ Accessible* acc = Acc();
+ MOZ_ASSERT(acc);
+ return acc->State() & states::CHECKABLE ||
+ acc->Role() == roles::TOGGLE_BUTTON;
+}
+
+bool uiaRawElmProvider::HasExpandCollapsePattern() {
+ Accessible* acc = Acc();
+ MOZ_ASSERT(acc);
+ return acc->State() & (states::EXPANDABLE | states::HASPOPUP);
+}
+
+bool uiaRawElmProvider::HasValuePattern() const {
+ Accessible* acc = Acc();
+ MOZ_ASSERT(acc);
+ if (acc->HasNumericValue() || acc->IsCombobox() || acc->IsHTMLLink() ||
+ acc->IsTextField()) {
+ return true;
+ }
+ const nsRoleMapEntry* roleMapEntry = acc->ARIARoleMap();
+ return roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox);
+}