diff options
Diffstat (limited to 'accessible/windows')
-rw-r--r-- | accessible/windows/ia2/ia2AccessibleTable.cpp | 14 | ||||
-rw-r--r-- | accessible/windows/ia2/ia2AccessibleTable.h | 2 | ||||
-rw-r--r-- | accessible/windows/ia2/ia2AccessibleTableCell.cpp | 2 | ||||
-rw-r--r-- | accessible/windows/ia2/ia2AccessibleTableCell.h | 2 | ||||
-rw-r--r-- | accessible/windows/msaa/IUnknownImpl.h | 42 | ||||
-rw-r--r-- | accessible/windows/msaa/LazyInstantiator.cpp | 82 | ||||
-rw-r--r-- | accessible/windows/msaa/LazyInstantiator.h | 27 | ||||
-rw-r--r-- | accessible/windows/msaa/Platform.cpp | 1 | ||||
-rw-r--r-- | accessible/windows/uia/UiaGrid.cpp | 151 | ||||
-rw-r--r-- | accessible/windows/uia/UiaGrid.h | 52 | ||||
-rw-r--r-- | accessible/windows/uia/UiaGridItem.cpp | 130 | ||||
-rw-r--r-- | accessible/windows/uia/UiaGridItem.h | 51 | ||||
-rw-r--r-- | accessible/windows/uia/moz.build | 3 | ||||
-rw-r--r-- | accessible/windows/uia/uiaRawElmProvider.cpp | 594 | ||||
-rw-r--r-- | accessible/windows/uia/uiaRawElmProvider.h | 75 |
15 files changed, 1183 insertions, 45 deletions
diff --git a/accessible/windows/ia2/ia2AccessibleTable.cpp b/accessible/windows/ia2/ia2AccessibleTable.cpp index 50bdc79967..9ce94e8348 100644 --- a/accessible/windows/ia2/ia2AccessibleTable.cpp +++ b/accessible/windows/ia2/ia2AccessibleTable.cpp @@ -21,7 +21,7 @@ using namespace mozilla::a11y; TableAccessible* ia2AccessibleTable::TableAcc() { - Accessible* acc = Acc(); + Accessible* acc = MsaaAccessible::Acc(); return acc ? acc->AsTable() : nullptr; } @@ -46,6 +46,18 @@ ia2AccessibleTable::QueryInterface(REFIID iid, void** ppv) { return S_OK; } + if (IID_IGridProvider == iid) { + *ppv = static_cast<IGridProvider*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + if (IID_ITableProvider == iid) { + *ppv = static_cast<ITableProvider*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + return ia2AccessibleHypertext::QueryInterface(iid, ppv); } diff --git a/accessible/windows/ia2/ia2AccessibleTable.h b/accessible/windows/ia2/ia2AccessibleTable.h index 622187c379..e8479b1733 100644 --- a/accessible/windows/ia2/ia2AccessibleTable.h +++ b/accessible/windows/ia2/ia2AccessibleTable.h @@ -12,6 +12,7 @@ #include "AccessibleTable2.h" #include "ia2AccessibleHypertext.h" #include "IUnknownImpl.h" +#include "UiaGrid.h" namespace mozilla { namespace a11y { @@ -20,6 +21,7 @@ class TableAccessible; class ia2AccessibleTable : public IAccessibleTable, public IAccessibleTable2, + public UiaGrid, public ia2AccessibleHypertext { public: // IUnknown diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.cpp b/accessible/windows/ia2/ia2AccessibleTableCell.cpp index 0204983a08..c8e98d6a2f 100644 --- a/accessible/windows/ia2/ia2AccessibleTableCell.cpp +++ b/accessible/windows/ia2/ia2AccessibleTableCell.cpp @@ -27,6 +27,8 @@ TableCellAccessible* ia2AccessibleTableCell::CellAcc() { // IUnknown IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleTableCell) IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleTableCell) +IMPL_IUNKNOWN_QUERY_IFACE(IGridItemProvider) +IMPL_IUNKNOWN_QUERY_IFACE(ITableItemProvider) IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(ia2AccessibleHypertext) //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.h b/accessible/windows/ia2/ia2AccessibleTableCell.h index 04e978cb96..6925cd7b17 100644 --- a/accessible/windows/ia2/ia2AccessibleTableCell.h +++ b/accessible/windows/ia2/ia2AccessibleTableCell.h @@ -11,12 +11,14 @@ #include "AccessibleTableCell.h" #include "ia2AccessibleHypertext.h" #include "IUnknownImpl.h" +#include "UiaGridItem.h" namespace mozilla { namespace a11y { class TableCellAccessible; class ia2AccessibleTableCell : public IAccessibleTableCell, + public UiaGridItem, public ia2AccessibleHypertext { public: // IUnknown diff --git a/accessible/windows/msaa/IUnknownImpl.h b/accessible/windows/msaa/IUnknownImpl.h index 7935ebedda..757bff44bf 100644 --- a/accessible/windows/msaa/IUnknownImpl.h +++ b/accessible/windows/msaa/IUnknownImpl.h @@ -47,31 +47,31 @@ class AutoRefCnt { } // namespace a11y } // namespace mozilla -#define DECL_IUNKNOWN \ - public: \ - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); \ - ULONG STDMETHODCALLTYPE AddRef() override { \ - MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ - ++mRefCnt; \ - return mRefCnt; \ - } \ - ULONG STDMETHODCALLTYPE Release() override { \ - MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ - --mRefCnt; \ - if (mRefCnt) return mRefCnt; \ - \ - delete this; \ - return 0; \ - } \ - \ - private: \ - mozilla::a11y::AutoRefCnt mRefCnt; \ - \ +#define DECL_IUNKNOWN \ + public: \ + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**) override; \ + ULONG STDMETHODCALLTYPE AddRef() override { \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + ++mRefCnt; \ + return mRefCnt; \ + } \ + ULONG STDMETHODCALLTYPE Release() override { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + --mRefCnt; \ + if (mRefCnt) return mRefCnt; \ + \ + delete this; \ + return 0; \ + } \ + \ + private: \ + mozilla::a11y::AutoRefCnt mRefCnt; \ + \ public: #define DECL_IUNKNOWN_INHERITED \ public: \ - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**) override; #define IMPL_IUNKNOWN_QUERY_HEAD(Class) \ STDMETHODIMP \ diff --git a/accessible/windows/msaa/LazyInstantiator.cpp b/accessible/windows/msaa/LazyInstantiator.cpp index 61814d059a..3ace26d9c0 100644 --- a/accessible/windows/msaa/LazyInstantiator.cpp +++ b/accessible/windows/msaa/LazyInstantiator.cpp @@ -12,6 +12,7 @@ #include "mozilla/a11y/Platform.h" #include "mozilla/Assertions.h" #include "mozilla/mscom/ProcessRuntime.h" +#include "mozilla/StaticPrefs_accessibility.h" #include "mozilla/UniquePtr.h" #include "mozilla/WinHeaderOnlyUtils.h" #include "MsaaRootAccessible.h" @@ -41,9 +42,9 @@ static const wchar_t kLazyInstantiatorProp[] = Maybe<bool> LazyInstantiator::sShouldBlockUia; -/* static */ -already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) { - RefPtr<IAccessible> result; +template <class T> +already_AddRefed<T> LazyInstantiator::GetRoot(HWND aHwnd) { + RefPtr<T> result; // At this time we only want to check whether the acc service is running. We // don't actually want to create the acc service yet. if (!GetAccService()) { @@ -80,7 +81,7 @@ already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) { if (!rootAcc->IsRoot()) { // rootAcc might represent a popup as opposed to a true root accessible. // In that case we just use the regular LocalAccessible::GetNativeInterface. - rootAcc->GetNativeInterface(getter_AddRefs(result)); + result = MsaaAccessible::GetFrom(rootAcc); return result.forget(); } @@ -90,7 +91,7 @@ already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) { // don't need LazyInstantiator's capabilities anymore (since a11y is already // running). We can bypass LazyInstantiator by retrieving the internal // unknown (which is not wrapped by the LazyInstantiator) and then querying - // that for IID_IAccessible. + // that for the interface we want. RefPtr<IUnknown> punk(msaaRoot->GetInternalUnknown()); MOZ_ASSERT(punk); @@ -98,10 +99,24 @@ already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) { return nullptr; } - punk->QueryInterface(IID_IAccessible, getter_AddRefs(result)); + punk->QueryInterface(__uuidof(T), getter_AddRefs(result)); return result.forget(); } +/* static */ +already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) { + return GetRoot<IAccessible>(aHwnd); +} + +/* static */ +already_AddRefed<IRawElementProviderSimple> LazyInstantiator::GetRootUia( + HWND aHwnd) { + if (!StaticPrefs::accessibility_uia_enable()) { + return nullptr; + } + return GetRoot<IRawElementProviderSimple>(aHwnd); +} + /** * When marshaling an interface, COM makes a whole bunch of QueryInterface * calls to determine what kind of marshaling the interface supports. We need @@ -135,7 +150,8 @@ LazyInstantiator::LazyInstantiator(HWND aHwnd) mAllowBlindAggregation(false), mWeakMsaaRoot(nullptr), mWeakAccessible(nullptr), - mWeakDispatch(nullptr) { + mWeakDispatch(nullptr), + mWeakUia(nullptr) { MOZ_ASSERT(aHwnd); // Assign ourselves as the designated LazyInstantiator for aHwnd DebugOnly<BOOL> setPropOk = @@ -374,9 +390,16 @@ LazyInstantiator::MaybeResolveRoot() { if (FAILED(hr)) { return hr; } - // mWeakAccessible is weak, so don't hold a strong ref mWeakAccessible->Release(); + if (StaticPrefs::accessibility_uia_enable()) { + hr = mRealRootUnk->QueryInterface(IID_IRawElementProviderSimple, + (void**)&mWeakUia); + if (FAILED(hr)) { + return hr; + } + mWeakUia->Release(); + } // Now that a11y is running, we don't need to remain registered with our // HWND anymore. @@ -401,6 +424,9 @@ IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, IAccessible) IMPL_IUNKNOWN_QUERY_IFACE(IAccessible) IMPL_IUNKNOWN_QUERY_IFACE(IDispatch) IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider) +if (StaticPrefs::accessibility_uia_enable()) { + IMPL_IUNKNOWN_QUERY_IFACE(IRawElementProviderSimple) +} // See EnableBlindAggregation for comments. if (!mAllowBlindAggregation) { return E_NOINTERFACE; @@ -771,5 +797,45 @@ LazyInstantiator::QueryService(REFGUID aServiceId, REFIID aServiceIid, return servProv->QueryService(aServiceId, aServiceIid, aOutInterface); } +STDMETHODIMP +LazyInstantiator::get_ProviderOptions( + __RPC__out enum ProviderOptions* aOptions) { + // This method is called before a UIA connection is fully established and thus + // before we can detect the client. We must not call RESOLVE_ROOT here because + // this might turn out to be a client we want to block. + if (!aOptions) { + return E_INVALIDARG; + } + *aOptions = uiaRawElmProvider::kProviderOptions; + return S_OK; +} + +STDMETHODIMP +LazyInstantiator::GetPatternProvider( + PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) { + RESOLVE_ROOT; + return mWeakUia->GetPatternProvider(aPatternId, aPatternProvider); +} + +STDMETHODIMP +LazyInstantiator::GetPropertyValue(PROPERTYID aPropertyId, + __RPC__out VARIANT* aPropertyValue) { + RESOLVE_ROOT; + return mWeakUia->GetPropertyValue(aPropertyId, aPropertyValue); +} + +STDMETHODIMP +LazyInstantiator::get_HostRawElementProvider( + __RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider) { + // This method is called before a UIA connection is fully established and thus + // before we can detect the client. We must not call RESOLVE_ROOT here because + // this might turn out to be a client we want to block. + if (!aRawElmProvider) { + return E_INVALIDARG; + } + *aRawElmProvider = nullptr; + return UiaHostProviderFromHwnd(mHwnd, aRawElmProvider); +} + } // namespace a11y } // namespace mozilla diff --git a/accessible/windows/msaa/LazyInstantiator.h b/accessible/windows/msaa/LazyInstantiator.h index 00fa4ba6ed..adbb0f70b5 100644 --- a/accessible/windows/msaa/LazyInstantiator.h +++ b/accessible/windows/msaa/LazyInstantiator.h @@ -13,6 +13,7 @@ #include "nsString.h" #include <oleacc.h> +#include <uiautomation.h> class nsIFile; @@ -29,10 +30,14 @@ class MsaaRootAccessible; * services in order to fulfill; and * (2) LazyInstantiator::ShouldInstantiate returns true. */ -class LazyInstantiator final : public IAccessible, public IServiceProvider { +class LazyInstantiator final : public IAccessible, + public IServiceProvider, + public IRawElementProviderSimple { public: [[nodiscard]] static already_AddRefed<IAccessible> GetRootAccessible( HWND aHwnd); + [[nodiscard]] static already_AddRefed<IRawElementProviderSimple> GetRootUia( + HWND aHwnd); static void EnableBlindAggregation(HWND aHwnd); // IUnknown @@ -83,6 +88,22 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider { STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aServiceIid, void** aOutInterface) override; + // IRawElementProviderSimple + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ProviderOptions( + /* [retval][out] */ __RPC__out enum ProviderOptions* aProviderOptions); + + virtual HRESULT STDMETHODCALLTYPE GetPatternProvider( + /* [in] */ PATTERNID aPatternId, + /* [retval][out] */ __RPC__deref_out_opt IUnknown** aPatternProvider); + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + /* [in] */ PROPERTYID aPropertyId, + /* [retval][out] */ __RPC__out VARIANT* aPropertyValue); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_HostRawElementProvider( + /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple** + aRawElmProvider); + /** * We cache the result of UIA detection because it could be expensive if a * client repeatedly queries us. This function is called to reset that cache @@ -117,6 +138,9 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider { void TransplantRefCnt(); void ClearProp(); + template <class T> + static already_AddRefed<T> GetRoot(HWND aHwnd); + private: mozilla::a11y::AutoRefCnt mRefCnt; HWND mHwnd; @@ -133,6 +157,7 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider { MsaaRootAccessible* mWeakMsaaRoot; IAccessible* mWeakAccessible; IDispatch* mWeakDispatch; + IRawElementProviderSimple* mWeakUia; static Maybe<bool> sShouldBlockUia; }; diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp index f4d1c7b176..eaf9e7c126 100644 --- a/accessible/windows/msaa/Platform.cpp +++ b/accessible/windows/msaa/Platform.cpp @@ -129,6 +129,7 @@ void a11y::PlatformShowHideEvent(Accessible* aTarget, Accessible*, bool aInsert, void a11y::PlatformSelectionEvent(Accessible* aTarget, Accessible*, uint32_t aType) { MsaaAccessible::FireWinEvent(aTarget, aType); + uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aTarget, aType); } static bool GetInstantiatorExecutable(const DWORD aPid, diff --git a/accessible/windows/uia/UiaGrid.cpp b/accessible/windows/uia/UiaGrid.cpp new file mode 100644 index 0000000000..2a22a1f4da --- /dev/null +++ b/accessible/windows/uia/UiaGrid.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ia2AccessibleTable.h" +#include "mozilla/a11y/TableAccessible.h" +#include "nsIAccessiblePivot.h" +#include "Pivot.h" +#include "UiaGrid.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +// Helpers + +// Used to search for all row and column headers in a table. This could be slow, +// as it potentially walks all cells in the table. However, it's unclear if, +// when or how often clients will use this. If this proves to be a performance +// problem, we will need to add methods to TableAccessible to get all row and +// column headers in a faster way. +class HeaderRule : public PivotRule { + public: + explicit HeaderRule(role aRole) : mRole(aRole) {} + + virtual uint16_t Match(Accessible* aAcc) override { + role accRole = aAcc->Role(); + if (accRole == mRole) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + if (accRole == roles::CAPTION || aAcc->IsTableCell()) { + return nsIAccessibleTraversalRule::FILTER_IGNORE | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } + + private: + role mRole; +}; + +static SAFEARRAY* GetAllHeaders(Accessible* aTable, role aRole) { + AutoTArray<Accessible*, 20> headers; + Pivot pivot(aTable); + HeaderRule rule(aRole); + for (Accessible* header = pivot.Next(aTable, rule); header; + header = pivot.Next(header, rule)) { + headers.AppendElement(header); + } + return AccessibleArrayToUiaArray(headers); +} + +// UiaGrid + +Accessible* UiaGrid::Acc() { + auto* ia2t = static_cast<ia2AccessibleTable*>(this); + return ia2t->MsaaAccessible::Acc(); +} + +TableAccessible* UiaGrid::TableAcc() { + Accessible* acc = Acc(); + return acc ? acc->AsTable() : nullptr; +} + +// IGridProvider methods + +STDMETHODIMP +UiaGrid::GetItem(int aRow, int aColumn, + __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + TableAccessible* table = TableAcc(); + if (!table) { + return CO_E_OBJNOTCONNECTED; + } + Accessible* cell = table->CellAt(aRow, aColumn); + if (!cell) { + return E_INVALIDARG; + } + RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(cell); + uia.forget(aRetVal); + return S_OK; +} + +STDMETHODIMP +UiaGrid::get_RowCount(__RPC__out int* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + TableAccessible* table = TableAcc(); + if (!table) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = table->RowCount(); + return S_OK; +} + +STDMETHODIMP +UiaGrid::get_ColumnCount(__RPC__out int* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + TableAccessible* table = TableAcc(); + if (!table) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = table->ColCount(); + return S_OK; +} + +// ITableProvider methods + +STDMETHODIMP +UiaGrid::GetRowHeaders(__RPC__deref_out_opt SAFEARRAY** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = GetAllHeaders(acc, roles::ROWHEADER); + return S_OK; +} + +STDMETHODIMP +UiaGrid::GetColumnHeaders(__RPC__deref_out_opt SAFEARRAY** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = GetAllHeaders(acc, roles::COLUMNHEADER); + return S_OK; +} + +STDMETHODIMP +UiaGrid::get_RowOrColumnMajor(__RPC__out enum RowOrColumnMajor* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + // HTML and ARIA tables are always in row major order. + *aRetVal = RowOrColumnMajor_RowMajor; + return S_OK; +} diff --git a/accessible/windows/uia/UiaGrid.h b/accessible/windows/uia/UiaGrid.h new file mode 100644 index 0000000000..c38442246c --- /dev/null +++ b/accessible/windows/uia/UiaGrid.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_UiaGrid_h__ +#define mozilla_a11y_UiaGrid_h__ + +#include "objbase.h" +#include "uiautomation.h" + +namespace mozilla::a11y { +class Accessible; +class TableAccessible; + +/** + * IGridProvider and ITableProvider implementations. + */ +class UiaGrid : public IGridProvider, public ITableProvider { + public: + // IGridProvider + virtual HRESULT STDMETHODCALLTYPE GetItem( + /* [in] */ int aRow, + /* [in] */ int aColumn, + /* [retval][out] */ + __RPC__deref_out_opt IRawElementProviderSimple** aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_RowCount( + /* [retval][out] */ __RPC__out int* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ColumnCount( + /* [retval][out] */ __RPC__out int* aRetVal); + + // ITableProvider + virtual HRESULT STDMETHODCALLTYPE GetRowHeaders( + /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal); + + virtual HRESULT STDMETHODCALLTYPE GetColumnHeaders( + /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_RowOrColumnMajor( + /* [retval][out] */ __RPC__out enum RowOrColumnMajor* aRetVal); + + private: + Accessible* Acc(); + TableAccessible* TableAcc(); +}; + +} // namespace mozilla::a11y + +#endif diff --git a/accessible/windows/uia/UiaGridItem.cpp b/accessible/windows/uia/UiaGridItem.cpp new file mode 100644 index 0000000000..89c56b8d45 --- /dev/null +++ b/accessible/windows/uia/UiaGridItem.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ia2AccessibleTableCell.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" +#include "UiaGridItem.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +// UiaGridItem + +TableCellAccessible* UiaGridItem::CellAcc() { + auto* derived = static_cast<ia2AccessibleTableCell*>(this); + Accessible* acc = derived->Acc(); + return acc ? acc->AsTableCell() : nullptr; +} + +// IGridItemProvider methods + +STDMETHODIMP +UiaGridItem::get_Row(__RPC__out int* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = cell->RowIdx(); + return S_OK; +} + +STDMETHODIMP +UiaGridItem::get_Column(__RPC__out int* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = cell->ColIdx(); + return S_OK; +} + +STDMETHODIMP +UiaGridItem::get_RowSpan(__RPC__out int* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = cell->RowExtent(); + return S_OK; +} + +STDMETHODIMP +UiaGridItem::get_ColumnSpan(__RPC__out int* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = cell->ColExtent(); + return S_OK; +} + +STDMETHODIMP +UiaGridItem::get_ContainingGrid( + __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + TableAccessible* table = cell->Table(); + if (!table) { + return E_FAIL; + } + Accessible* tableAcc = table->AsAccessible(); + RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(tableAcc); + uia.forget(aRetVal); + return S_OK; +} + +// ITableItemProvider methods + +STDMETHODIMP +UiaGridItem::GetRowHeaderItems(__RPC__deref_out_opt SAFEARRAY** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + AutoTArray<Accessible*, 10> cells; + cell->RowHeaderCells(&cells); + *aRetVal = AccessibleArrayToUiaArray(cells); + return S_OK; +} + +STDMETHODIMP +UiaGridItem::GetColumnHeaderItems(__RPC__deref_out_opt SAFEARRAY** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + TableCellAccessible* cell = CellAcc(); + if (!cell) { + return CO_E_OBJNOTCONNECTED; + } + AutoTArray<Accessible*, 10> cells; + cell->ColHeaderCells(&cells); + *aRetVal = AccessibleArrayToUiaArray(cells); + return S_OK; +} diff --git a/accessible/windows/uia/UiaGridItem.h b/accessible/windows/uia/UiaGridItem.h new file mode 100644 index 0000000000..6c59630b0f --- /dev/null +++ b/accessible/windows/uia/UiaGridItem.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_UiaGridItem_h__ +#define mozilla_a11y_UiaGridItem_h__ + +#include "objbase.h" +#include "uiautomation.h" + +namespace mozilla::a11y { +class TableCellAccessible; + +/** + * IGridItemProvider and ITableItemProvider implementations. + */ +class UiaGridItem : public IGridItemProvider, public ITableItemProvider { + public: + // IGridItemProvider + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Row( + /* [retval][out] */ __RPC__out int* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Column( + /* [retval][out] */ __RPC__out int* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_RowSpan( + /* [retval][out] */ __RPC__out int* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ColumnSpan( + /* [retval][out] */ __RPC__out int* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ContainingGrid( + /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple** + aRetVal); + + // ITableItemProvider + virtual HRESULT STDMETHODCALLTYPE GetRowHeaderItems( + /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal); + + virtual HRESULT STDMETHODCALLTYPE GetColumnHeaderItems( + /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal); + + private: + TableCellAccessible* CellAcc(); +}; + +} // namespace mozilla::a11y + +#endif diff --git a/accessible/windows/uia/moz.build b/accessible/windows/uia/moz.build index c52a24d612..2061261494 100644 --- a/accessible/windows/uia/moz.build +++ b/accessible/windows/uia/moz.build @@ -5,6 +5,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. SOURCES += [ + "UiaGrid.cpp", + "UiaGridItem.cpp", "uiaRawElmProvider.cpp", "UiaRoot.cpp", ] @@ -13,6 +15,7 @@ LOCAL_INCLUDES += [ "/accessible/base", "/accessible/generic", "/accessible/html", + "/accessible/windows/ia2", "/accessible/windows/msaa", "/accessible/xpcom", "/accessible/xul", diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp index c022e40cef..82726bb9aa 100644 --- a/accessible/windows/uia/uiaRawElmProvider.cpp +++ b/accessible/windows/uia/uiaRawElmProvider.cpp @@ -11,7 +11,10 @@ #include "AccAttributes.h" #include "AccessibleWrap.h" +#include "ApplicationAccessible.h" #include "ARIAMap.h" +#include "ia2AccessibleTable.h" +#include "ia2AccessibleTableCell.h" #include "LocalAccessible-inl.h" #include "mozilla/a11y/RemoteAccessible.h" #include "mozilla/StaticPrefs_accessibility.h" @@ -19,12 +22,25 @@ #include "MsaaRootAccessible.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" +#include "nsIAccessiblePivot.h" #include "nsTextEquivUtils.h" +#include "Pivot.h" +#include "Relation.h" #include "RootAccessible.h" using namespace mozilla; using namespace mozilla::a11y; +#ifdef __MINGW32__ +// These constants are missing in mingw-w64. This code should be removed once +// we update to a version which includes them. +const long UIA_CustomLandmarkTypeId = 80000; +const long UIA_FormLandmarkTypeId = 80001; +const long UIA_MainLandmarkTypeId = 80002; +const long UIA_NavigationLandmarkTypeId = 80003; +const long UIA_SearchLandmarkTypeId = 80004; +#endif // __MINGW32__ + // Helper functions static ToggleState ToToggleState(uint64_t aState) { @@ -51,6 +67,34 @@ static ExpandCollapseState ToExpandCollapseState(uint64_t aState) { return ExpandCollapseState_LeafNode; } +static bool IsRadio(Accessible* aAcc) { + role r = aAcc->Role(); + return r == roles::RADIOBUTTON || r == roles::RADIO_MENU_ITEM; +} + +// Used to search for a text leaf descendant for the LabeledBy property. +class LabelTextLeafRule : public PivotRule { + public: + virtual uint16_t Match(Accessible* aAcc) override { + if (aAcc->IsTextLeaf()) { + nsAutoString name; + aAcc->Name(name); + if (name.IsEmpty() || name.EqualsLiteral(" ")) { + // An empty or white space text leaf isn't useful as a label. + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + if (!nsTextEquivUtils::HasNameRule(aAcc, eNameFromSubtreeIfReqRule)) { + // Don't descend into things that can't be used as label content; e.g. + // text boxes. + return nsIAccessibleTraversalRule::FILTER_IGNORE | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } +}; + //////////////////////////////////////////////////////////////////////////////// // uiaRawElmProvider //////////////////////////////////////////////////////////////////////////////// @@ -84,12 +128,32 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc, case nsIAccessibleEvent::EVENT_NAME_CHANGE: property = UIA_NamePropertyId; break; + case nsIAccessibleEvent::EVENT_SELECTION: + ::UiaRaiseAutomationEvent(uia, UIA_SelectionItem_ElementSelectedEventId); + return; + case nsIAccessibleEvent::EVENT_SELECTION_ADD: + ::UiaRaiseAutomationEvent( + uia, UIA_SelectionItem_ElementAddedToSelectionEventId); + return; + case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: + ::UiaRaiseAutomationEvent( + uia, UIA_SelectionItem_ElementRemovedFromSelectionEventId); + return; + case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: + ::UiaRaiseAutomationEvent(uia, UIA_Selection_InvalidatedEventId); + return; case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: property = UIA_ValueValuePropertyId; newVal.vt = VT_BSTR; uia->get_Value(&newVal.bstrVal); gotNewVal = true; break; + case nsIAccessibleEvent::EVENT_VALUE_CHANGE: + property = UIA_RangeValueValuePropertyId; + newVal.vt = VT_R8; + uia->get_Value(&newVal.dblVal); + gotNewVal = true; + break; } if (property && ::UiaClientsAreListening()) { // We can't get the old value. Thankfully, clients don't seem to need it. @@ -117,6 +181,13 @@ void uiaRawElmProvider::RaiseUiaEventForStateChange(Accessible* aAcc, _variant_t newVal; switch (aState) { case states::CHECKED: + if (aEnabled && IsRadio(aAcc)) { + ::UiaRaiseAutomationEvent(uia, + UIA_SelectionItem_ElementSelectedEventId); + return; + } + // For other checkable things, the Toggle pattern is used. + [[fallthrough]]; case states::MIXED: case states::PRESSED: property = UIA_ToggleToggleStatePropertyId; @@ -161,8 +232,14 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) { *aInterface = static_cast<IExpandCollapseProvider*>(this); } else if (aIid == IID_IInvokeProvider) { *aInterface = static_cast<IInvokeProvider*>(this); + } else if (aIid == IID_IRangeValueProvider) { + *aInterface = static_cast<IRangeValueProvider*>(this); } else if (aIid == IID_IScrollItemProvider) { *aInterface = static_cast<IScrollItemProvider*>(this); + } else if (aIid == IID_ISelectionItemProvider) { + *aInterface = static_cast<ISelectionItemProvider*>(this); + } else if (aIid == IID_ISelectionProvider) { + *aInterface = static_cast<ISelectionProvider*>(this); } else if (aIid == IID_IToggleProvider) { *aInterface = static_cast<IToggleProvider*>(this); } else if (aIid == IID_IValueProvider) { @@ -248,9 +325,7 @@ uiaRawElmProvider::get_ProviderOptions( __RPC__out enum ProviderOptions* aOptions) { if (!aOptions) return E_INVALIDARG; - *aOptions = static_cast<enum ProviderOptions>( - ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading | - ProviderOptions_HasNativeIAccessible); + *aOptions = kProviderOptions; return S_OK; } @@ -270,21 +345,78 @@ uiaRawElmProvider::GetPatternProvider( expand.forget(aPatternProvider); } return S_OK; + case UIA_GridPatternId: + if (acc->IsTable()) { + auto grid = GetPatternFromDerived<ia2AccessibleTable, IGridProvider>(); + grid.forget(aPatternProvider); + } + return S_OK; + case UIA_GridItemPatternId: + if (acc->IsTableCell()) { + auto item = + GetPatternFromDerived<ia2AccessibleTableCell, IGridItemProvider>(); + item.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()) { + !HasExpandCollapsePattern() && !HasSelectionItemPattern()) { RefPtr<IInvokeProvider> invoke = this; invoke.forget(aPatternProvider); } return S_OK; + case UIA_RangeValuePatternId: + if (acc->HasNumericValue()) { + RefPtr<IValueProvider> value = this; + value.forget(aPatternProvider); + } + return S_OK; case UIA_ScrollItemPatternId: { RefPtr<IScrollItemProvider> scroll = this; scroll.forget(aPatternProvider); return S_OK; } + case UIA_SelectionItemPatternId: + if (HasSelectionItemPattern()) { + RefPtr<ISelectionItemProvider> item = this; + item.forget(aPatternProvider); + } + return S_OK; + case UIA_SelectionPatternId: + // According to the UIA documentation, radio button groups should support + // the Selection pattern. However: + // 1. The Core AAM spec doesn't specify the Selection pattern for + // the radiogroup role. + // 2. HTML radio buttons might not be contained by a dedicated group. + // 3. Chromium exposes the Selection pattern on radio groups, but it + // doesn't expose any selected items, even when there is a checked radio + // child. + // 4. Radio menu items are similar to radio buttons and all the above + // also applies to menus. + // For now, we don't support the Selection pattern for radio groups or + // menus, only for list boxes, tab lists, etc. + if (acc->IsSelect()) { + RefPtr<ISelectionProvider> selection = this; + selection.forget(aPatternProvider); + } + return S_OK; + case UIA_TablePatternId: + if (acc->IsTable()) { + auto table = + GetPatternFromDerived<ia2AccessibleTable, ITableProvider>(); + table.forget(aPatternProvider); + } + return S_OK; + case UIA_TableItemPatternId: + if (acc->IsTableCell()) { + auto item = + GetPatternFromDerived<ia2AccessibleTableCell, ITableItemProvider>(); + item.forget(aPatternProvider); + } + return S_OK; case UIA_TogglePatternId: if (HasTogglePattern()) { RefPtr<IToggleProvider> toggle = this; @@ -349,19 +481,19 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, break; } - // ARIA Role / shortcut case UIA_AriaRolePropertyId: { - nsAutoString xmlRoles; - - RefPtr<AccAttributes> attributes = acc->Attributes(); - attributes->GetAttribute(nsGkAtoms::xmlroles, xmlRoles); - - if (!xmlRoles.IsEmpty()) { + nsAutoString role; + if (acc->HasARIARole()) { + RefPtr<AccAttributes> attributes = acc->Attributes(); + attributes->GetAttribute(nsGkAtoms::xmlroles, role); + } else if (nsStaticAtom* computed = acc->ComputedARIARole()) { + computed->ToString(role); + } + if (!role.IsEmpty()) { aPropertyValue->vt = VT_BSTR; - aPropertyValue->bstrVal = ::SysAllocString(xmlRoles.get()); + aPropertyValue->bstrVal = ::SysAllocString(role.get()); return S_OK; } - break; } @@ -415,11 +547,57 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, break; } + case UIA_ClassNamePropertyId: { + nsAutoString className; + acc->DOMNodeClass(className); + if (!className.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(className.get()); + return S_OK; + } + break; + } + + case UIA_ControllerForPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = AccRelationsToUiaArray( + {RelationType::CONTROLLER_FOR, RelationType::ERRORMSG}); + return S_OK; + case UIA_ControlTypePropertyId: aPropertyValue->vt = VT_I4; aPropertyValue->lVal = GetControlType(); break; + case UIA_DescribedByPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = AccRelationsToUiaArray( + {RelationType::DESCRIBED_BY, RelationType::DETAILS}); + return S_OK; + + case UIA_FlowsFromPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = + AccRelationsToUiaArray({RelationType::FLOWS_FROM}); + return S_OK; + + case UIA_FlowsToPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = AccRelationsToUiaArray({RelationType::FLOWS_TO}); + return S_OK; + + case UIA_FrameworkIdPropertyId: + if (ApplicationAccessible* app = ApplicationAcc()) { + nsAutoString name; + app->PlatformName(name); + if (!name.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(name.get()); + return S_OK; + } + } + break; + case UIA_FullDescriptionPropertyId: { nsAutoString desc; acc->Description(desc); @@ -459,6 +637,39 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, (acc->State() & states::FOCUSABLE) ? VARIANT_TRUE : VARIANT_FALSE; return S_OK; + case UIA_LabeledByPropertyId: + if (Accessible* target = GetLabeledBy()) { + aPropertyValue->vt = VT_UNKNOWN; + RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(target); + uia.forget(&aPropertyValue->punkVal); + return S_OK; + } + break; + + case UIA_LandmarkTypePropertyId: + if (long type = GetLandmarkType()) { + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = type; + return S_OK; + } + break; + + case UIA_LevelPropertyId: + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = acc->GroupPosition().level; + return S_OK; + + case UIA_LocalizedLandmarkTypePropertyId: { + nsAutoString landmark; + GetLocalizedLandmarkType(landmark); + if (!landmark.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(landmark.get()); + return S_OK; + } + break; + } + case UIA_NamePropertyId: { nsAutoString name; acc->Name(name); @@ -469,6 +680,16 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, } break; } + + case UIA_PositionInSetPropertyId: + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = acc->GroupPosition().posInSet; + return S_OK; + + case UIA_SizeOfSetPropertyId: + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = acc->GroupPosition().setSize; + return S_OK; } return S_OK; @@ -749,6 +970,223 @@ uiaRawElmProvider::get_IsReadOnly(__RPC__out BOOL* aRetVal) { return S_OK; } +// IRangeValueProvider methods + +STDMETHODIMP +uiaRawElmProvider::SetValue(double aVal) { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (!acc->SetCurValue(aVal)) { + return UIA_E_INVALIDOPERATION; + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_Value(__RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->CurValue(); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_Maximum(__RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->MaxValue(); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_Minimum( + /* [retval][out] */ __RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->MinValue(); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_LargeChange( + /* [retval][out] */ __RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + // We want the change that would occur if the user pressed page up or page + // down. For HTML input elements, this is 10% of the total range unless step + // is larger. See: + // https://searchfox.org/mozilla-central/rev/c7df16ffad1f12a19c81c16bce0b65e4a15304d0/dom/html/HTMLInputElement.cpp#3878 + double step = acc->Step(); + double min = acc->MinValue(); + double max = acc->MaxValue(); + if (std::isnan(step) || std::isnan(min) || std::isnan(max)) { + *aRetVal = UnspecifiedNaN<double>(); + } else { + *aRetVal = std::max(step, (max - min) / 10); + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_SmallChange( + /* [retval][out] */ __RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->Step(); + return S_OK; +} + +// ISelectionProvider methods + +STDMETHODIMP +uiaRawElmProvider::GetSelection(__RPC__deref_out_opt SAFEARRAY** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + AutoTArray<Accessible*, 10> items; + acc->SelectedItems(&items); + *aRetVal = AccessibleArrayToUiaArray(items); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_CanSelectMultiple(__RPC__out BOOL* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->State() & states::MULTISELECTABLE; + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_IsSelectionRequired(__RPC__out BOOL* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->State() & states::REQUIRED; + return S_OK; +} + +// ISelectionItemProvider methods + +STDMETHODIMP +uiaRawElmProvider::Select() { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + acc->DoAction(0); + } else { + acc->TakeSelection(); + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::AddToSelection() { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + acc->DoAction(0); + } else { + acc->SetSelected(true); + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::RemoveFromSelection() { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + return UIA_E_INVALIDOPERATION; + } + acc->SetSelected(false); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_IsSelected(__RPC__out BOOL* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + *aRetVal = acc->State() & states::CHECKED; + } else { + *aRetVal = acc->State() & states::SELECTED; + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_SelectionContainer( + __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + Accessible* container = nsAccUtils::GetSelectableContainer(acc, acc->State()); + if (!container) { + return E_FAIL; + } + RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(container); + uia.forget(aRetVal); + return S_OK; +} + // Private methods bool uiaRawElmProvider::IsControl() { @@ -854,3 +1292,133 @@ bool uiaRawElmProvider::HasValuePattern() const { const nsRoleMapEntry* roleMapEntry = acc->ARIARoleMap(); return roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox); } + +template <class Derived, class Interface> +RefPtr<Interface> uiaRawElmProvider::GetPatternFromDerived() { + // MsaaAccessible inherits from uiaRawElmProvider. Derived + // inherits from MsaaAccessible and Interface. The compiler won't let us + // directly static_cast to Interface, hence the intermediate casts. + auto* msaa = static_cast<MsaaAccessible*>(this); + auto* derived = static_cast<Derived*>(msaa); + return derived; +} + +bool uiaRawElmProvider::HasSelectionItemPattern() { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + // In UIA, radio buttons and radio menu items are exposed as selected or + // unselected. + return acc->State() & states::SELECTABLE || IsRadio(acc); +} + +SAFEARRAY* uiaRawElmProvider::AccRelationsToUiaArray( + std::initializer_list<RelationType> aTypes) const { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + AutoTArray<Accessible*, 10> targets; + for (RelationType type : aTypes) { + Relation rel = acc->RelationByType(type); + while (Accessible* target = rel.Next()) { + targets.AppendElement(target); + } + } + return AccessibleArrayToUiaArray(targets); +} + +Accessible* uiaRawElmProvider::GetLabeledBy() const { + // Per the UIA documentation, some control types should never get a value for + // the LabeledBy property. + switch (GetControlType()) { + case UIA_ButtonControlTypeId: + case UIA_CheckBoxControlTypeId: + case UIA_DataItemControlTypeId: + case UIA_MenuControlTypeId: + case UIA_MenuBarControlTypeId: + case UIA_RadioButtonControlTypeId: + case UIA_ScrollBarControlTypeId: + case UIA_SeparatorControlTypeId: + case UIA_StatusBarControlTypeId: + case UIA_TabItemControlTypeId: + case UIA_TextControlTypeId: + case UIA_ToolBarControlTypeId: + case UIA_ToolTipControlTypeId: + case UIA_TreeItemControlTypeId: + return nullptr; + } + + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + // Even when LabeledBy is supported, it can only return a single "static text" + // element. + Relation rel = acc->RelationByType(RelationType::LABELLED_BY); + LabelTextLeafRule rule; + while (Accessible* target = rel.Next()) { + // If target were a text leaf, we should return that, but that shouldn't be + // possible because only an element (not a text node) can be the target of a + // relation. + MOZ_ASSERT(!target->IsTextLeaf()); + Pivot pivot(target); + if (Accessible* leaf = pivot.Next(target, rule)) { + return leaf; + } + } + return nullptr; +} + +long uiaRawElmProvider::GetLandmarkType() const { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + nsStaticAtom* landmark = acc->LandmarkRole(); + if (!landmark) { + return 0; + } + if (landmark == nsGkAtoms::form) { + return UIA_FormLandmarkTypeId; + } + if (landmark == nsGkAtoms::main) { + return UIA_MainLandmarkTypeId; + } + if (landmark == nsGkAtoms::navigation) { + return UIA_NavigationLandmarkTypeId; + } + if (landmark == nsGkAtoms::search) { + return UIA_SearchLandmarkTypeId; + } + return UIA_CustomLandmarkTypeId; +} + +void uiaRawElmProvider::GetLocalizedLandmarkType(nsAString& aLocalized) const { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + nsStaticAtom* landmark = acc->LandmarkRole(); + // The system provides strings for landmarks explicitly supported by the UIA + // LandmarkType property; i.e. form, main, navigation and search. We must + // provide strings for landmarks considered custom by UIA. For now, we only + // support landmarks in the core ARIA specification, not other ARIA modules + // such as DPub. + if (landmark == nsGkAtoms::banner || landmark == nsGkAtoms::complementary || + landmark == nsGkAtoms::contentinfo || landmark == nsGkAtoms::region) { + nsAutoString unlocalized; + landmark->ToString(unlocalized); + Accessible::TranslateString(unlocalized, aLocalized); + } +} + +SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) { + if (aAccs.IsEmpty()) { + // The UIA documentation is unclear about this, but the UIA client + // framework seems to treat a null value the same as an empty array. This + // is also what Chromium does. + return nullptr; + } + SAFEARRAY* uias = SafeArrayCreateVector(VT_UNKNOWN, 0, aAccs.Length()); + LONG indices[1] = {0}; + for (Accessible* acc : aAccs) { + // SafeArrayPutElement calls AddRef on the element, so we use a raw pointer + // here. + IRawElementProviderSimple* uia = MsaaAccessible::GetFrom(acc); + SafeArrayPutElement(uias, indices, uia); + ++indices[0]; + } + return uias; +} diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h index 0e05d1a030..ea713d8bc1 100644 --- a/accessible/windows/uia/uiaRawElmProvider.h +++ b/accessible/windows/uia/uiaRawElmProvider.h @@ -11,10 +11,20 @@ #include <stdint.h> #include <uiautomation.h> +#include <initializer_list> + +#include "nsString.h" + +template <class T> +class nsTArray; +template <class T> +class RefPtr; + namespace mozilla { namespace a11y { class Accessible; +enum class RelationType; /** * IRawElementProviderSimple implementation (maintains IAccessibleEx approach). @@ -26,8 +36,16 @@ class uiaRawElmProvider : public IAccessibleEx, public IToggleProvider, public IExpandCollapseProvider, public IScrollItemProvider, - public IValueProvider { + public IValueProvider, + public IRangeValueProvider, + public ISelectionProvider, + public ISelectionItemProvider { public: + static constexpr enum ProviderOptions kProviderOptions = + static_cast<enum ProviderOptions>(ProviderOptions_ServerSideProvider | + ProviderOptions_UseComThreading | + ProviderOptions_HasNativeIAccessible); + static void RaiseUiaEventForGeckoEvent(Accessible* aAcc, uint32_t aGeckoEvent); static void RaiseUiaEventForStateChange(Accessible* aAcc, uint64_t aState, @@ -118,6 +136,51 @@ class uiaRawElmProvider : public IAccessibleEx, virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsReadOnly( /* [retval][out] */ __RPC__out BOOL* pRetVal); + // IRangeValueProvider + virtual HRESULT STDMETHODCALLTYPE SetValue( + /* [in] */ double aVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Value( + /* [retval][out] */ __RPC__out double* aRetVal); + + // get_IsReadOnly is shared with IValueProvider. + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Maximum( + /* [retval][out] */ __RPC__out double* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Minimum( + /* [retval][out] */ __RPC__out double* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_LargeChange( + /* [retval][out] */ __RPC__out double* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_SmallChange( + /* [retval][out] */ __RPC__out double* aRetVal); + + // ISelectionProvider + virtual HRESULT STDMETHODCALLTYPE GetSelection( + /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_CanSelectMultiple( + /* [retval][out] */ __RPC__out BOOL* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsSelectionRequired( + /* [retval][out] */ __RPC__out BOOL* aRetVal); + + // ISelectionItemProvider methods + virtual HRESULT STDMETHODCALLTYPE Select(void); + + virtual HRESULT STDMETHODCALLTYPE AddToSelection(void); + + virtual HRESULT STDMETHODCALLTYPE RemoveFromSelection(void); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsSelected( + /* [retval][out] */ __RPC__out BOOL* aRetVal); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_SelectionContainer( + /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple** + aRetVal); + private: Accessible* Acc() const; bool IsControl(); @@ -125,8 +188,18 @@ class uiaRawElmProvider : public IAccessibleEx, bool HasTogglePattern(); bool HasExpandCollapsePattern(); bool HasValuePattern() const; + template <class Derived, class Interface> + RefPtr<Interface> GetPatternFromDerived(); + bool HasSelectionItemPattern(); + SAFEARRAY* AccRelationsToUiaArray( + std::initializer_list<RelationType> aTypes) const; + Accessible* GetLabeledBy() const; + long GetLandmarkType() const; + void GetLocalizedLandmarkType(nsAString& aLocalized) const; }; +SAFEARRAY* AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs); + } // namespace a11y } // namespace mozilla |