diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /accessible/windows | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
85 files changed, 12579 insertions, 0 deletions
diff --git a/accessible/windows/ProxyWrappers.h b/accessible/windows/ProxyWrappers.h new file mode 100644 index 0000000000..38b0981b09 --- /dev/null +++ b/accessible/windows/ProxyWrappers.h @@ -0,0 +1,131 @@ +/* -*- 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_ProxyWrappers_h +#define MOZILLA_A11Y_ProxyWrappers_h + +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +class ProxyAccessibleWrap : public AccessibleWrap { + public: + explicit ProxyAccessibleWrap(ProxyAccessible* aProxy) + : AccessibleWrap(nullptr, nullptr) { + mType = eProxyType; + mBits.proxy = aProxy; + } + + virtual void Shutdown() override { + mBits.proxy = nullptr; + mStateFlags |= eIsDefunct; + } + + virtual void GetNativeInterface(void** aOutAccessible) override { + mBits.proxy->GetCOMInterface(aOutAccessible); + } +}; + +class HyperTextProxyAccessibleWrap : public HyperTextAccessibleWrap { + public: + explicit HyperTextProxyAccessibleWrap(ProxyAccessible* aProxy) + : HyperTextAccessibleWrap(nullptr, nullptr) { + mType = eProxyType; + mBits.proxy = aProxy; + } + + virtual void Shutdown() override { + mBits.proxy = nullptr; + mStateFlags |= eIsDefunct; + } + + virtual void GetNativeInterface(void** aOutAccessible) override { + mBits.proxy->GetCOMInterface(aOutAccessible); + } +}; + +class DocProxyAccessibleWrap : public HyperTextProxyAccessibleWrap { + public: + explicit DocProxyAccessibleWrap(ProxyAccessible* aProxy) + : HyperTextProxyAccessibleWrap(aProxy) { + mGenericTypes |= eDocument; + } + + void AddID(uint32_t aID, AccessibleWrap* aAcc) { + mIDToAccessibleMap.Put(aID, aAcc); + } + void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); } + AccessibleWrap* GetAccessibleByID(uint32_t aID) const { + return mIDToAccessibleMap.Get(aID); + } + + virtual nsIntRect Bounds() const override { + // OuterDocAccessible can return a DocProxyAccessibleWrap as a child. + // Accessible::ChildAtPoint on an ancestor might retrieve this proxy and + // call Bounds() on it. This will crash on a proxy, so we override it to do + // nothing here. + return nsIntRect(); + } + + private: + /* + * This provides a mapping from 32 bit id to accessible objects. + */ + nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap; +}; + +template <typename T> +inline ProxyAccessible* HyperTextProxyFor(T* aWrapper) { + static_assert(std::is_base_of<IUnknown, T>::value, + "only IAccessible* should be passed in"); + auto wrapper = static_cast<HyperTextProxyAccessibleWrap*>(aWrapper); + return wrapper->IsProxy() ? wrapper->Proxy() : nullptr; +} + +/** + * Stub AccessibleWrap used in a content process for an embedded document + * residing in another content process. + * There is no ProxyAccessible here, since those only exist in the parent + * process. However, like ProxyAccessibleWrap, the only real method that + * gets called is GetNativeInterface, which returns a COM proxy for the + * document. + */ +class RemoteIframeDocProxyAccessibleWrap : public HyperTextAccessibleWrap { + public: + explicit RemoteIframeDocProxyAccessibleWrap(IDispatch* aCOMProxy) + : HyperTextAccessibleWrap(nullptr, nullptr), mCOMProxy(aCOMProxy) { + mType = eProxyType; + mBits.proxy = nullptr; + } + + virtual void Shutdown() override { + mStateFlags |= eIsDefunct; + mCOMProxy = nullptr; + } + + virtual void GetNativeInterface(void** aOutAccessible) override { + RefPtr<IDispatch> addRefed = mCOMProxy; + addRefed.forget(aOutAccessible); + } + + virtual nsIntRect Bounds() const override { + // OuterDocAccessible can return a RemoteIframeDocProxyAccessibleWrap as a + // child. Accessible::ChildAtPoint on an ancestor might retrieve this proxy + // and call Bounds() on it. This will crash on a proxy, so we override it + // to do nothing here. + return nsIntRect(); + } + + private: + RefPtr<IDispatch> mCOMProxy; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2Accessible.cpp b/accessible/windows/ia2/ia2Accessible.cpp new file mode 100644 index 0000000000..b042e41913 --- /dev/null +++ b/accessible/windows/ia2/ia2Accessible.cpp @@ -0,0 +1,632 @@ +/* -*- 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 "AccessibleWrap.h" + +#include "Accessible2_i.c" +#include "Accessible2_2_i.c" +#include "Accessible2_3_i.c" +#include "AccessibleRole.h" +#include "AccessibleStates.h" + +#include "Compatibility.h" +#include "ia2AccessibleRelation.h" +#include "IUnknownImpl.h" +#include "nsCoreUtils.h" +#include "nsIAccessibleTypes.h" +#include "mozilla/a11y/PDocAccessible.h" +#include "Relation.h" +#include "TextRange-inl.h" +#include "nsAccessibilityService.h" + +#include "mozilla/PresShell.h" +#include "nsIPersistentProperties2.h" +#include "nsISimpleEnumerator.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +template <typename String> +static void EscapeAttributeChars(String& aStr); + +//////////////////////////////////////////////////////////////////////////////// +// ia2Accessible +//////////////////////////////////////////////////////////////////////////////// + +STDMETHODIMP +ia2Accessible::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + // NOTE: If any new versions of IAccessible2 are added here, they should + // also be added to the IA2 Handler in + // /accessible/ipc/win/handler/AccessibleHandler.cpp + + if (IID_IAccessible2_3 == iid) + *ppv = static_cast<IAccessible2_3*>(this); + else if (IID_IAccessible2_2 == iid) + *ppv = static_cast<IAccessible2_2*>(this); + else if (IID_IAccessible2 == iid) + *ppv = static_cast<IAccessible2*>(this); + + if (*ppv) { + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//////////////////////////////////////////////////////////////////////////////// +// IAccessible2 + +STDMETHODIMP +ia2Accessible::get_nRelations(long* aNRelations) { + if (!aNRelations) return E_INVALIDARG; + *aNRelations = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + MOZ_ASSERT(!acc->IsProxy()); + + for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { + if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; + + Relation rel = acc->RelationByType(sRelationTypePairs[idx].first); + if (rel.Next()) (*aNRelations)++; + } + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_relation(long aRelationIndex, + IAccessibleRelation** aRelation) { + if (!aRelation || aRelationIndex < 0) return E_INVALIDARG; + *aRelation = nullptr; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + MOZ_ASSERT(!acc->IsProxy()); + + long relIdx = 0; + for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { + if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; + + RelationType relationType = sRelationTypePairs[idx].first; + Relation rel = acc->RelationByType(relationType); + RefPtr<ia2AccessibleRelation> ia2Relation = + new ia2AccessibleRelation(relationType, &rel); + if (ia2Relation->HasTargets()) { + if (relIdx == aRelationIndex) { + acc->AssociateCOMObjectForDisconnection(ia2Relation); + ia2Relation.forget(aRelation); + return S_OK; + } + + relIdx++; + } + } + + return E_INVALIDARG; +} + +STDMETHODIMP +ia2Accessible::get_relations(long aMaxRelations, + IAccessibleRelation** aRelation, + long* aNRelations) { + if (!aRelation || !aNRelations || aMaxRelations <= 0) return E_INVALIDARG; + *aNRelations = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + MOZ_ASSERT(!acc->IsProxy()); + + for (uint32_t idx = 0; + idx < ArrayLength(sRelationTypePairs) && *aNRelations < aMaxRelations; + idx++) { + if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; + + RelationType relationType = sRelationTypePairs[idx].first; + Relation rel = acc->RelationByType(relationType); + RefPtr<ia2AccessibleRelation> ia2Rel = + new ia2AccessibleRelation(relationType, &rel); + if (ia2Rel->HasTargets()) { + acc->AssociateCOMObjectForDisconnection(ia2Rel); + ia2Rel.forget(aRelation + (*aNRelations)); + (*aNRelations)++; + } + } + return S_OK; +} + +STDMETHODIMP +ia2Accessible::role(long* aRole) { + if (!aRole) return E_INVALIDARG; + *aRole = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + +#define ROLE(_geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \ + ia2Role, androidClass, nameRule) \ + case roles::_geckoRole: \ + *aRole = ia2Role; \ + break; + + a11y::role geckoRole; + MOZ_ASSERT(!acc->IsProxy()); + geckoRole = acc->Role(); + switch (geckoRole) { +#include "RoleMap.h" + default: + MOZ_CRASH("Unknown role."); + } + +#undef ROLE + + // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call + // the IA2 role a ROLE_OUTLINEITEM. + MOZ_ASSERT(!acc->IsProxy()); + if (geckoRole == roles::ROW) { + Accessible* xpParent = acc->Parent(); + if (xpParent && xpParent->Role() == roles::TREE_TABLE) + *aRole = ROLE_SYSTEM_OUTLINEITEM; + } + + return S_OK; +} + +// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294. +MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP +ia2Accessible::scrollTo(enum IA2ScrollType aScrollType) { + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + MOZ_ASSERT(!acc->IsProxy()); + RefPtr<PresShell> presShell = acc->Document()->PresShellPtr(); + nsCOMPtr<nsIContent> content = acc->GetContent(); + nsCoreUtils::ScrollTo(presShell, content, aScrollType); + + return S_OK; +} + +STDMETHODIMP +ia2Accessible::scrollToPoint(enum IA2CoordinateType aCoordType, long aX, + long aY) { + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + uint32_t geckoCoordType = + (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) + ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; + + MOZ_ASSERT(!acc->IsProxy()); + acc->ScrollToPoint(geckoCoordType, aX, aY); + + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_groupPosition(long* aGroupLevel, long* aSimilarItemsInGroup, + long* aPositionInGroup) { + if (!aGroupLevel || !aSimilarItemsInGroup || !aPositionInGroup) + return E_INVALIDARG; + + *aGroupLevel = 0; + *aSimilarItemsInGroup = 0; + *aPositionInGroup = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + GroupPos groupPos = acc->GroupPosition(); + + // Group information for accessibles having level only (like html headings + // elements) isn't exposed by this method. AT should look for 'level' object + // attribute. + if (!groupPos.setSize && !groupPos.posInSet) return S_FALSE; + + *aGroupLevel = groupPos.level; + *aSimilarItemsInGroup = groupPos.setSize; + *aPositionInGroup = groupPos.posInSet; + + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_states(AccessibleStates* aStates) { + if (!aStates) return E_INVALIDARG; + *aStates = 0; + + // XXX: bug 344674 should come with better approach that we have here. + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) { + *aStates = IA2_STATE_DEFUNCT; + return S_OK; + } + + uint64_t state; + MOZ_ASSERT(!acc->IsProxy()); + state = acc->State(); + + if (state & states::INVALID) *aStates |= IA2_STATE_INVALID_ENTRY; + if (state & states::REQUIRED) *aStates |= IA2_STATE_REQUIRED; + + // The following IA2 states are not supported by Gecko + // IA2_STATE_ARMED + // IA2_STATE_MANAGES_DESCENDANTS + // IA2_STATE_ICONIFIED + // IA2_STATE_INVALID // This is not a state, it is the absence of a state + + if (state & states::ACTIVE) *aStates |= IA2_STATE_ACTIVE; + if (state & states::DEFUNCT) *aStates |= IA2_STATE_DEFUNCT; + if (state & states::EDITABLE) *aStates |= IA2_STATE_EDITABLE; + if (state & states::HORIZONTAL) *aStates |= IA2_STATE_HORIZONTAL; + if (state & states::MODAL) *aStates |= IA2_STATE_MODAL; + if (state & states::MULTI_LINE) *aStates |= IA2_STATE_MULTI_LINE; + if (state & states::OPAQUE1) *aStates |= IA2_STATE_OPAQUE; + if (state & states::SELECTABLE_TEXT) *aStates |= IA2_STATE_SELECTABLE_TEXT; + if (state & states::SINGLE_LINE) *aStates |= IA2_STATE_SINGLE_LINE; + if (state & states::STALE) *aStates |= IA2_STATE_STALE; + if (state & states::SUPPORTS_AUTOCOMPLETION) + *aStates |= IA2_STATE_SUPPORTS_AUTOCOMPLETION; + if (state & states::TRANSIENT) *aStates |= IA2_STATE_TRANSIENT; + if (state & states::VERTICAL) *aStates |= IA2_STATE_VERTICAL; + if (state & states::CHECKED) *aStates |= IA2_STATE_CHECKABLE; + if (state & states::PINNED) *aStates |= IA2_STATE_PINNED; + + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_extendedRole(BSTR* aExtendedRole) { + if (!aExtendedRole) return E_INVALIDARG; + + *aExtendedRole = nullptr; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2Accessible::get_localizedExtendedRole(BSTR* aLocalizedExtendedRole) { + if (!aLocalizedExtendedRole) return E_INVALIDARG; + + *aLocalizedExtendedRole = nullptr; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2Accessible::get_nExtendedStates(long* aNExtendedStates) { + if (!aNExtendedStates) return E_INVALIDARG; + + *aNExtendedStates = 0; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2Accessible::get_extendedStates(long aMaxExtendedStates, + BSTR** aExtendedStates, + long* aNExtendedStates) { + if (!aExtendedStates || !aNExtendedStates) return E_INVALIDARG; + + *aExtendedStates = nullptr; + *aNExtendedStates = 0; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2Accessible::get_localizedExtendedStates(long aMaxLocalizedExtendedStates, + BSTR** aLocalizedExtendedStates, + long* aNLocalizedExtendedStates) { + if (!aLocalizedExtendedStates || !aNLocalizedExtendedStates) + return E_INVALIDARG; + + *aLocalizedExtendedStates = nullptr; + *aNLocalizedExtendedStates = 0; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2Accessible::get_uniqueID(long* aUniqueID) { + if (!aUniqueID) return E_INVALIDARG; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + *aUniqueID = AccessibleWrap::GetChildIDFor(acc); + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_windowHandle(HWND* aWindowHandle) { + if (!aWindowHandle) return E_INVALIDARG; + *aWindowHandle = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aWindowHandle = AccessibleWrap::GetHWNDFor(acc); + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_indexInParent(long* aIndexInParent) { + if (!aIndexInParent) return E_INVALIDARG; + *aIndexInParent = -1; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + MOZ_ASSERT(!acc->IsProxy()); + *aIndexInParent = acc->IndexInParent(); + + if (*aIndexInParent == -1) return S_FALSE; + + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_locale(IA2Locale* aLocale) { + if (!aLocale) return E_INVALIDARG; + + // Language codes consist of a primary code and a possibly empty series of + // subcodes: language-code = primary-code ( "-" subcode )* + // Two-letter primary codes are reserved for [ISO639] language abbreviations. + // Any two-letter subcode is understood to be a [ISO3166] country code. + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString lang; + acc->Language(lang); + + // If primary code consists from two letters then expose it as language. + int32_t offset = lang.FindChar('-', 0); + if (offset == -1) { + if (lang.Length() == 2) { + aLocale->language = ::SysAllocString(lang.get()); + return S_OK; + } + } else if (offset == 2) { + aLocale->language = ::SysAllocStringLen(lang.get(), 2); + + // If the first subcode consists from two letters then expose it as + // country. + offset = lang.FindChar('-', 3); + if (offset == -1) { + if (lang.Length() == 5) { + aLocale->country = ::SysAllocString(lang.get() + 3); + return S_OK; + } + } else if (offset == 5) { + aLocale->country = ::SysAllocStringLen(lang.get() + 3, 2); + } + } + + // Expose as a string if primary code or subcode cannot point to language or + // country abbreviations or if there are more than one subcode. + aLocale->variant = ::SysAllocString(lang.get()); + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_attributes(BSTR* aAttributes) { + if (!aAttributes) return E_INVALIDARG; + *aAttributes = nullptr; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + // The format is name:value;name:value; with \ for escaping these + // characters ":;=,\". + if (!acc->IsProxy()) { + nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes(); + return ConvertToIA2Attributes(attributes, aAttributes); + } + + MOZ_ASSERT(!acc->IsProxy()); + return E_UNEXPECTED; +} + +//////////////////////////////////////////////////////////////////////////////// +// IAccessible2_2 + +STDMETHODIMP +ia2Accessible::get_attribute(BSTR name, VARIANT* aAttribute) { + if (!aAttribute) return E_INVALIDARG; + + return E_NOTIMPL; +} + +STDMETHODIMP +ia2Accessible::get_accessibleWithCaret(IUnknown** aAccessible, + long* aCaretOffset) { + if (!aAccessible || !aCaretOffset) return E_INVALIDARG; + + *aAccessible = nullptr; + *aCaretOffset = -1; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + int32_t caretOffset = -1; + Accessible* accWithCaret = SelectionMgr()->AccessibleWithCaret(&caretOffset); + if (!accWithCaret || acc->Document() != accWithCaret->Document()) + return S_FALSE; + + Accessible* child = accWithCaret; + while (!child->IsDoc() && child != acc) child = child->Parent(); + + if (child != acc) return S_FALSE; + + *aAccessible = + static_cast<IAccessible2*>(static_cast<AccessibleWrap*>(accWithCaret)); + (*aAccessible)->AddRef(); + *aCaretOffset = caretOffset; + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_relationTargetsOfType(BSTR aType, long aMaxTargets, + IUnknown*** aTargets, + long* aNTargets) { + if (!aTargets || !aNTargets || aMaxTargets < 0) return E_INVALIDARG; + *aNTargets = 0; + + Maybe<RelationType> relationType; + for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { + if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) { + relationType.emplace(sRelationTypePairs[idx].first); + break; + } + } + if (!relationType) return E_INVALIDARG; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsTArray<Accessible*> targets; + MOZ_ASSERT(!acc->IsProxy()); + Relation rel = acc->RelationByType(*relationType); + Accessible* target = nullptr; + while ( + (target = rel.Next()) && + (aMaxTargets == 0 || static_cast<long>(targets.Length()) < aMaxTargets)) { + targets.AppendElement(target); + } + + *aNTargets = targets.Length(); + *aTargets = + static_cast<IUnknown**>(::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets)); + if (!*aTargets) return E_OUTOFMEMORY; + + for (int32_t i = 0; i < *aNTargets; i++) { + AccessibleWrap* target = static_cast<AccessibleWrap*>(targets[i]); + (*aTargets)[i] = static_cast<IAccessible2*>(target); + (*aTargets)[i]->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +ia2Accessible::get_selectionRanges(IA2Range** aRanges, long* aNRanges) { + if (!aRanges || !aNRanges) return E_INVALIDARG; + + *aNRanges = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + AutoTArray<TextRange, 1> ranges; + acc->Document()->SelectionRanges(&ranges); + ranges.RemoveElementsBy([acc](auto& range) { return !range.Crop(acc); }); + + *aNRanges = ranges.Length(); + *aRanges = + static_cast<IA2Range*>(::CoTaskMemAlloc(sizeof(IA2Range) * *aNRanges)); + if (!*aRanges) return E_OUTOFMEMORY; + + for (uint32_t idx = 0; idx < static_cast<uint32_t>(*aNRanges); idx++) { + AccessibleWrap* anchor = + static_cast<AccessibleWrap*>(ranges[idx].StartContainer()); + (*aRanges)[idx].anchor = static_cast<IAccessible2*>(anchor); + anchor->AddRef(); + + (*aRanges)[idx].anchorOffset = ranges[idx].StartOffset(); + + AccessibleWrap* active = + static_cast<AccessibleWrap*>(ranges[idx].EndContainer()); + (*aRanges)[idx].active = static_cast<IAccessible2*>(active); + active->AddRef(); + + (*aRanges)[idx].activeOffset = ranges[idx].EndOffset(); + } + + return S_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helpers + +template <typename String> +static inline void EscapeAttributeChars(String& aStr) { + int32_t offset = 0; + static const char kCharsToEscape[] = ":;=,\\"; + while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { + aStr.Insert('\\', offset); + offset += 2; + } +} + +HRESULT +ia2Accessible::ConvertToIA2Attributes(nsTArray<Attribute>* aAttributes, + BSTR* aIA2Attributes) { + nsString attrStr; + size_t attrCount = aAttributes->Length(); + for (size_t i = 0; i < attrCount; i++) { + EscapeAttributeChars(aAttributes->ElementAt(i).Name()); + EscapeAttributeChars(aAttributes->ElementAt(i).Value()); + AppendUTF8toUTF16(aAttributes->ElementAt(i).Name(), attrStr); + attrStr.Append(':'); + attrStr.Append(aAttributes->ElementAt(i).Value()); + attrStr.Append(';'); + } + + if (attrStr.IsEmpty()) return S_FALSE; + + *aIA2Attributes = ::SysAllocStringLen(attrStr.get(), attrStr.Length()); + return *aIA2Attributes ? S_OK : E_OUTOFMEMORY; +} + +HRESULT +ia2Accessible::ConvertToIA2Attributes(nsIPersistentProperties* aAttributes, + BSTR* aIA2Attributes) { + *aIA2Attributes = nullptr; + + // The format is name:value;name:value; with \ for escaping these + // characters ":;=,\". + + if (!aAttributes) return S_FALSE; + + nsCOMPtr<nsISimpleEnumerator> propEnum; + aAttributes->Enumerate(getter_AddRefs(propEnum)); + if (!propEnum) return E_FAIL; + + nsAutoString strAttrs; + + bool hasMore = false; + while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> propSupports; + propEnum->GetNext(getter_AddRefs(propSupports)); + + nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(propSupports)); + if (!propElem) return E_FAIL; + + nsAutoCString name; + if (NS_FAILED(propElem->GetKey(name))) return E_FAIL; + + EscapeAttributeChars(name); + + nsAutoString value; + if (NS_FAILED(propElem->GetValue(value))) return E_FAIL; + + EscapeAttributeChars(value); + + AppendUTF8toUTF16(name, strAttrs); + strAttrs.Append(':'); + strAttrs.Append(value); + strAttrs.Append(';'); + } + + if (strAttrs.IsEmpty()) return S_FALSE; + + *aIA2Attributes = ::SysAllocStringLen(strAttrs.get(), strAttrs.Length()); + return *aIA2Attributes ? S_OK : E_OUTOFMEMORY; +} diff --git a/accessible/windows/ia2/ia2Accessible.h b/accessible/windows/ia2/ia2Accessible.h new file mode 100644 index 0000000000..3a8afe46b4 --- /dev/null +++ b/accessible/windows/ia2/ia2Accessible.h @@ -0,0 +1,120 @@ +/* -*- 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_ia2Accessible_h_ +#define mozilla_a11y_ia2Accessible_h_ + +#include "nsISupports.h" + +#include "Accessible2_3.h" + +namespace mozilla { +namespace a11y { +class Attribute; + +class ia2Accessible : public IAccessible2_3 { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessible2 + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nRelations( + /* [retval][out] */ long* nRelations); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relation( + /* [in] */ long relationIndex, + /* [retval][out] */ IAccessibleRelation** relation); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relations( + /* [in] */ long maxRelations, + /* [length_is][size_is][out] */ IAccessibleRelation** relation, + /* [retval][out] */ long* nRelations); + + virtual HRESULT STDMETHODCALLTYPE role( + /* [retval][out] */ long* role); + + virtual HRESULT STDMETHODCALLTYPE scrollTo( + /* [in] */ enum IA2ScrollType scrollType); + + virtual HRESULT STDMETHODCALLTYPE scrollToPoint( + /* [in] */ enum IA2CoordinateType coordinateType, + /* [in] */ long x, + /* [in] */ long y); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_groupPosition( + /* [out] */ long* groupLevel, + /* [out] */ long* similarItemsInGroup, + /* [retval][out] */ long* positionInGroup); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_states( + /* [retval][out] */ AccessibleStates* states); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_extendedRole( + /* [retval][out] */ BSTR* extendedRole); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedExtendedRole( + /* [retval][out] */ BSTR* localizedExtendedRole); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nExtendedStates( + /* [retval][out] */ long* nExtendedStates); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_extendedStates( + /* [in] */ long maxExtendedStates, + /* [length_is][length_is][size_is][size_is][out] */ BSTR** extendedStates, + /* [retval][out] */ long* nExtendedStates); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedExtendedStates( + /* [in] */ long maxLocalizedExtendedStates, + /* [length_is][length_is][size_is][size_is][out] */ + BSTR** localizedExtendedStates, + /* [retval][out] */ long* nLocalizedExtendedStates); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_uniqueID( + /* [retval][out] */ long* uniqueID); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_windowHandle( + /* [retval][out] */ HWND* windowHandle); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_indexInParent( + /* [retval][out] */ long* indexInParent); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_locale( + /* [retval][out] */ IA2Locale* locale); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_attributes( + /* [retval][out] */ BSTR* attributes); + + // IAccessible2_2 + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_attribute( + /* [in] */ BSTR name, + /* [out, retval] */ VARIANT* attribute); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_accessibleWithCaret( + /* [out] */ IUnknown** accessible, + /* [out, retval] */ long* caretOffset); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationTargetsOfType( + /* [in] */ BSTR type, + /* [in] */ long maxTargets, + /* [out, size_is(,*nTargets)] */ IUnknown*** targets, + /* [out, retval] */ long* nTargets); + + // IAccessible2_3 + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectionRanges( + /* [out, size_is(,*nRanges)] */ IA2Range** ranges, + /* [out, retval] */ long* nRanges); + + // Helper method + static HRESULT ConvertToIA2Attributes(nsIPersistentProperties* aAttributes, + BSTR* aIA2Attributes); + static HRESULT ConvertToIA2Attributes(nsTArray<Attribute>* aAttributes, + BSTR* aIA2Attributes); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleAction.cpp b/accessible/windows/ia2/ia2AccessibleAction.cpp new file mode 100644 index 0000000000..4671a48256 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleAction.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleAction.h" + +#include "AccessibleAction_i.c" + +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" + +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleAction::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleAction == iid && + !static_cast<AccessibleWrap*>(this)->IsProxy()) { + *ppv = static_cast<IAccessibleAction*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +// IAccessibleAction + +STDMETHODIMP +ia2AccessibleAction::nActions(long* aActionCount) { + if (!aActionCount) return E_INVALIDARG; + + *aActionCount = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aActionCount = acc->ActionCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleAction::doAction(long aActionIndex) { + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + uint8_t index = static_cast<uint8_t>(aActionIndex); + return acc->DoAction(index) ? S_OK : E_INVALIDARG; +} + +STDMETHODIMP +ia2AccessibleAction::get_description(long aActionIndex, BSTR* aDescription) { + if (!aDescription) return E_INVALIDARG; + *aDescription = nullptr; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString description; + uint8_t index = static_cast<uint8_t>(aActionIndex); + acc->ActionDescriptionAt(index, description); + if (description.IsEmpty()) return S_FALSE; + + *aDescription = ::SysAllocStringLen(description.get(), description.Length()); + return *aDescription ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleAction::get_keyBinding(long aActionIndex, long aNumMaxBinding, + BSTR** aKeyBinding, long* aNumBinding) { + if (!aKeyBinding) return E_INVALIDARG; + *aKeyBinding = nullptr; + + if (!aNumBinding) return E_INVALIDARG; + *aNumBinding = 0; + + if (aActionIndex != 0 || aNumMaxBinding < 1) return E_INVALIDARG; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + // Expose keyboard shortcut if it's not exposed via MSAA keyboard shortcut. + KeyBinding keyBinding = acc->AccessKey(); + if (keyBinding.IsEmpty()) return S_FALSE; + + keyBinding = acc->KeyboardShortcut(); + if (keyBinding.IsEmpty()) return S_FALSE; + + nsAutoString keyStr; + keyBinding.ToString(keyStr); + + *aKeyBinding = static_cast<BSTR*>(::CoTaskMemAlloc(sizeof(BSTR*))); + if (!*aKeyBinding) return E_OUTOFMEMORY; + + *(aKeyBinding[0]) = ::SysAllocStringLen(keyStr.get(), keyStr.Length()); + if (!*(aKeyBinding[0])) { + ::CoTaskMemFree(*aKeyBinding); + return E_OUTOFMEMORY; + } + + *aNumBinding = 1; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleAction::get_name(long aActionIndex, BSTR* aName) { + if (!aName) return E_INVALIDARG; + + *aName = nullptr; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString name; + uint8_t index = static_cast<uint8_t>(aActionIndex); + acc->ActionNameAt(index, name); + if (name.IsEmpty()) return E_INVALIDARG; + + *aName = ::SysAllocStringLen(name.get(), name.Length()); + return *aName ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleAction::get_localizedName(long aActionIndex, + BSTR* aLocalizedName) { + if (!aLocalizedName) return E_INVALIDARG; + + *aLocalizedName = nullptr; + return E_NOTIMPL; +} diff --git a/accessible/windows/ia2/ia2AccessibleAction.h b/accessible/windows/ia2/ia2AccessibleAction.h new file mode 100644 index 0000000000..e85c345844 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleAction.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_ACTION_H +#define _ACCESSIBLE_ACTION_H + +#include "nsISupports.h" + +#include "AccessibleAction.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleAction : public IAccessibleAction { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleAction + virtual HRESULT STDMETHODCALLTYPE nActions( + /* [retval][out] */ long* nActions); + + virtual HRESULT STDMETHODCALLTYPE doAction( + /* [in] */ long actionIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_description( + /* [in] */ long actionIndex, + /* [retval][out] */ BSTR* description); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_keyBinding( + /* [in] */ long actionIndex, + /* [in] */ long nMaxBinding, + /* [length_is][length_is][size_is][size_is][out] */ BSTR** keyBinding, + /* [retval][out] */ long* nBinding); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_name( + /* [in] */ long actionIndex, + /* [retval][out] */ BSTR* name); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedName( + /* [in] */ long actionIndex, + /* [retval][out] */ BSTR* localizedName); +}; + +} // namespace a11y +} // namespace mozilla + +#define FORWARD_IACCESSIBLEACTION(Class) \ + virtual HRESULT STDMETHODCALLTYPE nActions(long* nActions) { \ + return Class::nActions(nActions); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE doAction(long actionIndex) { \ + return Class::doAction(actionIndex); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_description(long actionIndex, \ + BSTR* description) { \ + return Class::get_description(actionIndex, description); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_keyBinding( \ + long actionIndex, long nMaxBinding, BSTR** keyBinding, long* nBinding) { \ + return Class::get_keyBinding(actionIndex, nMaxBinding, keyBinding, \ + nBinding); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_name(long actionIndex, BSTR* name) { \ + return Class::get_name(actionIndex, name); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_localizedName(long actionIndex, \ + BSTR* localizedName) { \ + return Class::get_localizedName(actionIndex, localizedName); \ + } + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleComponent.cpp b/accessible/windows/ia2/ia2AccessibleComponent.cpp new file mode 100644 index 0000000000..9e6996cc12 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleComponent.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleComponent.h" + +#include "AccessibleComponent_i.c" + +#include "AccessibleWrap.h" +#include "States.h" +#include "IUnknownImpl.h" + +#include "nsIFrame.h" + +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleComponent::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleComponent == iid) { + *ppv = static_cast<IAccessibleComponent*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +// IAccessibleComponent + +STDMETHODIMP +ia2AccessibleComponent::get_locationInParent(long* aX, long* aY) { + if (!aX || !aY) return E_INVALIDARG; + + *aX = 0; + *aY = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + // If the object is not on any screen the returned position is (0,0). + uint64_t state = acc->State(); + if (state & states::INVISIBLE) return S_OK; + + nsIntRect rect = acc->Bounds(); + + // The coordinates of the returned position are relative to this object's + // parent or relative to the screen on which this object is rendered if it + // has no parent. + if (!acc->Parent()) { + *aX = rect.X(); + *aY = rect.Y(); + return S_OK; + } + + // The coordinates of the bounding box are given relative to the parent's + // coordinate system. + nsIntRect parentRect = acc->Parent()->Bounds(); + *aX = rect.X() - parentRect.X(); + *aY = rect.Y() - parentRect.Y(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleComponent::get_foreground(IA2Color* aForeground) { + if (!aForeground) return E_INVALIDARG; + + *aForeground = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsIFrame* frame = acc->GetFrame(); + if (frame) *aForeground = frame->StyleText()->mColor.ToColor(); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleComponent::get_background(IA2Color* aBackground) { + if (!aBackground) return E_INVALIDARG; + + *aBackground = 0; + + AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsIFrame* frame = acc->GetFrame(); + if (frame) { + *aBackground = frame->StyleBackground()->BackgroundColor(frame); + } + + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleComponent.h b/accessible/windows/ia2/ia2AccessibleComponent.h new file mode 100644 index 0000000000..54e87d1963 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleComponent.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 IA2_ACCESSIBLE_COMPONENT_H_ +#define IA2_ACCESSIBLE_COMPONENT_H_ + +#include "AccessibleComponent.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleComponent : public IAccessibleComponent { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleComponent + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_locationInParent( + /* [out] */ long* x, + /* [retval][out] */ long* y); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_foreground( + /* [retval][out] */ IA2Color* foreground); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_background( + /* [retval][out] */ IA2Color* background); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleEditableText.cpp b/accessible/windows/ia2/ia2AccessibleEditableText.cpp new file mode 100644 index 0000000000..06c1dc1530 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleEditableText.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleEditableText.h" + +#include "AccessibleEditableText_i.c" +#include "HyperTextAccessible-inl.h" +#include "HyperTextAccessibleWrap.h" +#include "ProxyWrappers.h" + +#include "nsCOMPtr.h" +#include "nsString.h" + +using namespace mozilla::a11y; + +// IAccessibleEditableText + +STDMETHODIMP +ia2AccessibleEditableText::copyText(long aStartOffset, long aEndOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) return E_INVALIDARG; + + textAcc->CopyText(aStartOffset, aEndOffset); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleEditableText::deleteText(long aStartOffset, long aEndOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) return E_INVALIDARG; + + textAcc->DeleteText(aStartOffset, aEndOffset); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleEditableText::insertText(long aOffset, BSTR* aText) { + uint32_t length = ::SysStringLen(*aText); + nsAutoString text(*aText, length); + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; + + textAcc->InsertText(text, aOffset); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleEditableText::cutText(long aStartOffset, long aEndOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) return E_INVALIDARG; + + textAcc->CutText(aStartOffset, aEndOffset); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleEditableText::pasteText(long aOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + RefPtr<HyperTextAccessible> textAcc = + static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; + + textAcc->PasteText(aOffset); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleEditableText::replaceText(long aStartOffset, long aEndOffset, + BSTR* aText) { + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) return E_INVALIDARG; + + textAcc->DeleteText(aStartOffset, aEndOffset); + + uint32_t length = ::SysStringLen(*aText); + nsAutoString text(*aText, length); + textAcc->InsertText(text, aStartOffset); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleEditableText::setAttributes(long aStartOffset, long aEndOffset, + BSTR* aAttributes) { + return E_NOTIMPL; +} diff --git a/accessible/windows/ia2/ia2AccessibleEditableText.h b/accessible/windows/ia2/ia2AccessibleEditableText.h new file mode 100644 index 0000000000..7c6470773d --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleEditableText.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_EDITABLETEXT_H +#define _ACCESSIBLE_EDITABLETEXT_H + +#include "nsISupports.h" + +#include "AccessibleEditableText.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleEditableText : public IAccessibleEditableText { + public: + // IAccessibleEditableText + virtual HRESULT STDMETHODCALLTYPE copyText( + /* [in] */ long startOffset, + /* [in] */ long endOffset); + + virtual HRESULT STDMETHODCALLTYPE deleteText( + /* [in] */ long startOffset, + /* [in] */ long endOffset); + + virtual HRESULT STDMETHODCALLTYPE insertText( + /* [in] */ long offset, + /* [in] */ BSTR* text); + + virtual HRESULT STDMETHODCALLTYPE cutText( + /* [in] */ long startOffset, + /* [in] */ long endOffset); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual HRESULT STDMETHODCALLTYPE pasteText( + /* [in] */ long offset); + + virtual HRESULT STDMETHODCALLTYPE replaceText( + /* [in] */ long startOffset, + /* [in] */ long endOffset, + /* [in] */ BSTR* text); + + virtual HRESULT STDMETHODCALLTYPE setAttributes( + /* [in] */ long startOffset, + /* [in] */ long endOffset, + /* [in] */ BSTR* attributes); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleHyperlink.cpp b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp new file mode 100644 index 0000000000..2943b3e530 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "AccessibleHyperlink.h" +#include "AccessibleHyperlink_i.c" + +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" +#include "nsIURI.h" + +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleHyperlink::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleHyperlink == iid) { + auto accWrap = static_cast<AccessibleWrap*>(this); + if (accWrap->IsProxy() + ? !(accWrap->ProxyInterfaces() & Interfaces::HYPERLINK) + : !accWrap->IsLink()) + return E_NOINTERFACE; + + *ppv = static_cast<IAccessibleHyperlink*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return ia2AccessibleAction::QueryInterface(iid, ppv); +} + +// IAccessibleHyperlink + +STDMETHODIMP +ia2AccessibleHyperlink::get_anchor(long aIndex, VARIANT* aAnchor) { + if (!aAnchor) return E_INVALIDARG; + + VariantInit(aAnchor); + + Accessible* thisObj = static_cast<AccessibleWrap*>(this); + MOZ_ASSERT(!thisObj->IsProxy()); + + if (thisObj->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (aIndex < 0 || aIndex >= static_cast<long>(thisObj->AnchorCount())) + return E_INVALIDARG; + + if (!thisObj->IsLink()) return S_FALSE; + + AccessibleWrap* anchor = + static_cast<AccessibleWrap*>(thisObj->AnchorAt(aIndex)); + if (!anchor) return S_FALSE; + + void* instancePtr = nullptr; + HRESULT result = anchor->QueryInterface(IID_IUnknown, &instancePtr); + if (FAILED(result)) return result; + + aAnchor->punkVal = static_cast<IUnknown*>(instancePtr); + aAnchor->vt = VT_UNKNOWN; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleHyperlink::get_anchorTarget(long aIndex, VARIANT* aAnchorTarget) { + if (!aAnchorTarget) { + return E_INVALIDARG; + } + + VariantInit(aAnchorTarget); + + Accessible* thisObj = static_cast<AccessibleWrap*>(this); + nsAutoCString uriStr; + MOZ_ASSERT(!thisObj->IsProxy()); + if (thisObj->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + if (aIndex < 0 || aIndex >= static_cast<long>(thisObj->AnchorCount())) { + return E_INVALIDARG; + } + + if (!thisObj->IsLink()) { + return S_FALSE; + } + + nsCOMPtr<nsIURI> uri = thisObj->AnchorURIAt(aIndex); + if (!uri) { + return S_FALSE; + } + + nsresult rv = uri->GetSpec(uriStr); + if (NS_FAILED(rv)) { + return GetHRESULT(rv); + } + + nsAutoString stringURI; + AppendUTF8toUTF16(uriStr, stringURI); + + aAnchorTarget->vt = VT_BSTR; + aAnchorTarget->bstrVal = + ::SysAllocStringLen(stringURI.get(), stringURI.Length()); + return aAnchorTarget->bstrVal ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleHyperlink::get_startIndex(long* aIndex) { + if (!aIndex) return E_INVALIDARG; + + *aIndex = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + Accessible* thisObj = static_cast<AccessibleWrap*>(this); + if (thisObj->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!thisObj->IsLink()) return S_FALSE; + + *aIndex = thisObj->StartOffset(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleHyperlink::get_endIndex(long* aIndex) { + if (!aIndex) return E_INVALIDARG; + + *aIndex = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + Accessible* thisObj = static_cast<AccessibleWrap*>(this); + if (thisObj->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!thisObj->IsLink()) return S_FALSE; + + *aIndex = thisObj->EndOffset(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleHyperlink::get_valid(boolean* aValid) { + if (!aValid) return E_INVALIDARG; + + *aValid = false; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + Accessible* thisObj = static_cast<AccessibleWrap*>(this); + if (thisObj->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!thisObj->IsLink()) return S_FALSE; + + *aValid = thisObj->IsLinkValid(); + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleHyperlink.h b/accessible/windows/ia2/ia2AccessibleHyperlink.h new file mode 100644 index 0000000000..c469255e1c --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleHyperlink.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_HYPERLINK_H +#define _ACCESSIBLE_HYPERLINK_H + +#include "nsISupports.h" + +#include "ia2AccessibleAction.h" +#include "AccessibleHyperlink.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleHyperlink : public ia2AccessibleAction, + public IAccessibleHyperlink { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleAction + FORWARD_IACCESSIBLEACTION(ia2AccessibleAction) + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_anchor( + /* [in] */ long index, + /* [retval][out] */ VARIANT* anchor); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_anchorTarget( + /* [in] */ long index, + /* [retval][out] */ VARIANT* anchorTarget); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_startIndex( + /* [retval][out] */ long* index); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_endIndex( + /* [retval][out] */ long* index); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_valid( + /* [retval][out] */ boolean* valid); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleHypertext.cpp b/accessible/windows/ia2/ia2AccessibleHypertext.cpp new file mode 100644 index 0000000000..eed2c2cdf6 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleHypertext.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleHypertext.h" + +#include "AccessibleHypertext_i.c" + +#include "HyperTextAccessibleWrap.h" +#include "IUnknownImpl.h" + +using namespace mozilla::a11y; + +// IAccessibleHypertext + +STDMETHODIMP +ia2AccessibleHypertext::get_nHyperlinks(long* aHyperlinkCount) { + if (!aHyperlinkCount) return E_INVALIDARG; + + *aHyperlinkCount = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessibleWrap* hyperText = + static_cast<HyperTextAccessibleWrap*>(this); + if (hyperText->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aHyperlinkCount = hyperText->LinkCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleHypertext::get_hyperlink(long aLinkIndex, + IAccessibleHyperlink** aHyperlink) { + if (!aHyperlink) return E_INVALIDARG; + + *aHyperlink = nullptr; + + AccessibleWrap* hyperLink; + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessibleWrap* hyperText = + static_cast<HyperTextAccessibleWrap*>(this); + if (hyperText->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + hyperLink = static_cast<AccessibleWrap*>(hyperText->LinkAt(aLinkIndex)); + + if (!hyperLink) return E_FAIL; + + *aHyperlink = static_cast<IAccessibleHyperlink*>(hyperLink); + (*aHyperlink)->AddRef(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleHypertext::get_hyperlinkIndex(long aCharIndex, + long* aHyperlinkIndex) { + if (!aHyperlinkIndex) return E_INVALIDARG; + + *aHyperlinkIndex = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessibleWrap* hyperAcc = + static_cast<HyperTextAccessibleWrap*>(this); + if (hyperAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aHyperlinkIndex = hyperAcc->LinkIndexAtOffset(aCharIndex); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleHypertext::get_hyperlinks(IAccessibleHyperlink*** aHyperlinks, + long* aNHyperlinks) { + if (!aHyperlinks || !aNHyperlinks) { + return E_INVALIDARG; + } + + *aHyperlinks = nullptr; + *aNHyperlinks = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessibleWrap* hyperText = + static_cast<HyperTextAccessibleWrap*>(this); + if (hyperText->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + uint32_t count = hyperText->LinkCount(); + *aNHyperlinks = count; + + if (count == 0) { + *aHyperlinks = nullptr; + return S_FALSE; + } + + *aHyperlinks = static_cast<IAccessibleHyperlink**>( + ::CoTaskMemAlloc(sizeof(IAccessibleHyperlink*) * count)); + if (!*aHyperlinks) { + return E_OUTOFMEMORY; + } + + for (uint32_t i = 0; i < count; ++i) { + AccessibleWrap* hyperLink = + static_cast<AccessibleWrap*>(hyperText->LinkAt(i)); + MOZ_ASSERT(hyperLink); + (*aHyperlinks)[i] = static_cast<IAccessibleHyperlink*>(hyperLink); + (*aHyperlinks)[i]->AddRef(); + } + + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleHypertext.h b/accessible/windows/ia2/ia2AccessibleHypertext.h new file mode 100644 index 0000000000..2cdda4cf75 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleHypertext.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_HYPERTEXT_H +#define _ACCESSIBLE_HYPERTEXT_H + +#include "nsISupports.h" + +#include "ia2AccessibleText.h" +#include "AccessibleHypertext2.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleHypertext : public ia2AccessibleText, + public IAccessibleHypertext2 { + public: + // IAccessibleText + FORWARD_IACCESSIBLETEXT(ia2AccessibleText) + + // IAccessibleHypertext + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nHyperlinks( + /* [retval][out] */ long* hyperlinkCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_hyperlink( + /* [in] */ long index, + /* [retval][out] */ IAccessibleHyperlink** hyperlink); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_hyperlinkIndex( + /* [in] */ long charIndex, + /* [retval][out] */ long* hyperlinkIndex); + + // IAccessibleHypertext2 + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_hyperlinks( + /* [out, size_is(,*nHyperlinks)] */ IAccessibleHyperlink*** hyperlinks, + /* [out, retval] */ long* nHyperlinks); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleImage.cpp b/accessible/windows/ia2/ia2AccessibleImage.cpp new file mode 100644 index 0000000000..ad198bb759 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleImage.cpp @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleImage.h" + +#include "AccessibleImage_i.c" + +#include "ImageAccessibleWrap.h" +#include "IUnknownImpl.h" +#include "nsIAccessibleTypes.h" + +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleImage::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleImage == iid) { + *ppv = static_cast<IAccessibleImage*>(this); + (static_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +// IAccessibleImage + +STDMETHODIMP +ia2AccessibleImage::get_description(BSTR* aDescription) { + if (!aDescription) return E_INVALIDARG; + + *aDescription = nullptr; + + ImageAccessibleWrap* acc = static_cast<ImageAccessibleWrap*>(this); + if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString description; + acc->Name(description); + if (description.IsEmpty()) return S_FALSE; + + *aDescription = ::SysAllocStringLen(description.get(), description.Length()); + return *aDescription ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleImage::get_imagePosition(enum IA2CoordinateType aCoordType, + long* aX, long* aY) { + if (!aX || !aY) return E_INVALIDARG; + + *aX = 0; + *aY = 0; + + ImageAccessibleWrap* imageAcc = static_cast<ImageAccessibleWrap*>(this); + if (imageAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + uint32_t geckoCoordType = + (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) + ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; + + nsIntPoint pos = imageAcc->Position(geckoCoordType); + *aX = pos.x; + *aY = pos.y; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleImage::get_imageSize(long* aHeight, long* aWidth) { + if (!aHeight || !aWidth) return E_INVALIDARG; + + *aHeight = 0; + *aWidth = 0; + + ImageAccessibleWrap* imageAcc = static_cast<ImageAccessibleWrap*>(this); + if (imageAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsIntSize size = imageAcc->Size(); + *aHeight = size.width; + *aWidth = size.height; + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleImage.h b/accessible/windows/ia2/ia2AccessibleImage.h new file mode 100644 index 0000000000..23c7b74f77 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleImage.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_IMAGE_H +#define _ACCESSIBLE_IMAGE_H + +#include "AccessibleImage.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleImage : public IAccessibleImage { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleImage + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_description( + /* [retval][out] */ BSTR* description); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_imagePosition( + /* [in] */ enum IA2CoordinateType coordinateType, + /* [out] */ long* x, + /* [retval][out] */ long* y); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_imageSize( + /* [out] */ long* height, + /* [retval][out] */ long* width); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleRelation.cpp b/accessible/windows/ia2/ia2AccessibleRelation.cpp new file mode 100644 index 0000000000..1e920c5376 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleRelation.cpp @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleRelation.h" + +#include "Relation.h" +#include "nsID.h" + +#include "AccessibleRelation_i.c" + +using namespace mozilla::a11y; + +ia2AccessibleRelation::ia2AccessibleRelation(RelationType aType, Relation* aRel) + : mType(aType) { + Accessible* target = nullptr; + while ((target = aRel->Next())) mTargets.AppendElement(target); +} + +// IUnknown + +IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleRelation) +IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleRelation) +IMPL_IUNKNOWN_QUERY_IFACE(IUnknown) +IMPL_IUNKNOWN_QUERY_TAIL + +// IAccessibleRelation + +STDMETHODIMP +ia2AccessibleRelation::get_relationType(BSTR* aRelationType) { + if (!aRelationType) return E_INVALIDARG; + + *aRelationType = nullptr; + +#define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \ + case RelationType::geckoType: \ + *aRelationType = ::SysAllocString(ia2Type); \ + break; + + switch (mType) { +#include "RelationTypeMap.h" + } + + return *aRelationType ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleRelation::get_localizedRelationType(BSTR* aLocalizedRelationType) { + if (!aLocalizedRelationType) return E_INVALIDARG; + + *aLocalizedRelationType = nullptr; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2AccessibleRelation::get_nTargets(long* aNTargets) { + if (!aNTargets) return E_INVALIDARG; + + *aNTargets = mTargets.Length(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleRelation::get_target(long aTargetIndex, IUnknown** aTarget) { + if (aTargetIndex < 0 || (uint32_t)aTargetIndex >= mTargets.Length() || + !aTarget) + return E_INVALIDARG; + + AccessibleWrap* target = + static_cast<AccessibleWrap*>(mTargets[aTargetIndex].get()); + *aTarget = static_cast<IAccessible*>(target); + (*aTarget)->AddRef(); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleRelation::get_targets(long aMaxTargets, IUnknown** aTargets, + long* aNTargets) { + if (!aNTargets || !aTargets) return E_INVALIDARG; + + *aNTargets = 0; + long maxTargets = mTargets.Length(); + if (maxTargets > aMaxTargets) maxTargets = aMaxTargets; + + for (long idx = 0; idx < maxTargets; idx++) get_target(idx, aTargets + idx); + + *aNTargets = maxTargets; + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleRelation.h b/accessible/windows/ia2/ia2AccessibleRelation.h new file mode 100644 index 0000000000..afed4be56e --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleRelation.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _NS_ACCESSIBLE_RELATION_WRAP_H +#define _NS_ACCESSIBLE_RELATION_WRAP_H + +#include "Accessible.h" +#include "IUnknownImpl.h" + +#include <utility> +#include "nsTArray.h" + +#include "AccessibleRelation.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleRelation final : public IAccessibleRelation { + public: + ia2AccessibleRelation(RelationType aType, Relation* aRel); + + ia2AccessibleRelation(RelationType aType, + nsTArray<RefPtr<Accessible>>&& aTargets) + : mType(aType), mTargets(std::move(aTargets)) {} + + // IUnknown + DECL_IUNKNOWN + + // IAccessibleRelation + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationType( + /* [retval][out] */ BSTR* relationType); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedRelationType( + /* [retval][out] */ BSTR* localizedRelationType); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nTargets( + /* [retval][out] */ long* nTargets); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_target( + /* [in] */ long targetIndex, + /* [retval][out] */ IUnknown** target); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_targets( + /* [in] */ long maxTargets, + /* [length_is][size_is][out] */ IUnknown** target, + /* [retval][out] */ long* nTargets); + + inline bool HasTargets() const { return mTargets.Length(); } + + private: + ia2AccessibleRelation(); + ia2AccessibleRelation(const ia2AccessibleRelation&); + ia2AccessibleRelation& operator=(const ia2AccessibleRelation&); + + RelationType mType; + nsTArray<RefPtr<Accessible>> mTargets; +}; + +/** + * Gecko to IAccessible2 relation types map. + */ + +const WCHAR* const IA2_RELATION_NULL = L""; + +#define RELATIONTYPE(geckoType, name, atkType, msaaType, ia2Type) \ + std::pair<RelationType, const WCHAR* const>(RelationType::geckoType, ia2Type), + +static const std::pair<RelationType, const WCHAR* const> sRelationTypePairs[] = + { +#include "RelationTypeMap.h" +}; + +#undef RELATIONTYPE + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleTable.cpp b/accessible/windows/ia2/ia2AccessibleTable.cpp new file mode 100644 index 0000000000..7150c13da2 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleTable.cpp @@ -0,0 +1,540 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "Accessible2.h" +#include "AccessibleTable_i.c" +#include "AccessibleTable2_i.c" + +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" +#include "Statistics.h" +#include "TableAccessible.h" + +#include "nsCOMPtr.h" +#include "nsString.h" + +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleTable::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleTable == iid) { + statistics::IAccessibleTableUsed(); + *ppv = static_cast<IAccessibleTable*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + if (IID_IAccessibleTable2 == iid) { + *ppv = static_cast<IAccessibleTable2*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//////////////////////////////////////////////////////////////////////////////// +// IAccessibleTable + +STDMETHODIMP +ia2AccessibleTable::get_accessibleAt(long aRowIdx, long aColIdx, + IUnknown** aAccessible) { + return get_cellAt(aRowIdx, aColIdx, aAccessible); +} + +STDMETHODIMP +ia2AccessibleTable::get_caption(IUnknown** aAccessible) { + if (!aAccessible) return E_INVALIDARG; + + *aAccessible = nullptr; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + AccessibleWrap* caption = static_cast<AccessibleWrap*>(mTable->Caption()); + if (!caption) return S_FALSE; + + (*aAccessible = static_cast<IAccessible*>(caption))->AddRef(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_childIndex(long aRowIdx, long aColIdx, + long* aChildIdx) { + if (!aChildIdx) return E_INVALIDARG; + + *aChildIdx = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || aColIdx < 0 || + static_cast<uint32_t>(aRowIdx) >= mTable->RowCount() || + static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + *aChildIdx = mTable->CellIndexAt(aRowIdx, aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_columnDescription(long aColIdx, BSTR* aDescription) { + if (!aDescription) return E_INVALIDARG; + + *aDescription = nullptr; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + nsAutoString descr; + mTable->ColDescription(aColIdx, descr); + if (descr.IsEmpty()) return S_FALSE; + + *aDescription = ::SysAllocStringLen(descr.get(), descr.Length()); + return *aDescription ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleTable::get_columnExtentAt(long aRowIdx, long aColIdx, + long* aSpan) { + if (!aSpan) return E_INVALIDARG; + + *aSpan = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || aColIdx < 0 || + static_cast<uint32_t>(aRowIdx) >= mTable->RowCount() || + static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + *aSpan = mTable->ColExtentAt(aRowIdx, aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_columnHeader(IAccessibleTable** aAccessibleTable, + long* aStartingRowIndex) { + if (!aAccessibleTable || !aStartingRowIndex) return E_INVALIDARG; + + *aAccessibleTable = nullptr; + *aStartingRowIndex = -1; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2AccessibleTable::get_columnIndex(long aCellIdx, long* aColIdx) { + if (!aColIdx) return E_INVALIDARG; + + *aColIdx = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aCellIdx < 0) { + return E_INVALIDARG; + } + + long colIdx = mTable->ColIndexAt(aCellIdx); + if (colIdx == -1) { // Indicates an error. + return E_INVALIDARG; + } + + *aColIdx = colIdx; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_nColumns(long* aColCount) { + if (!aColCount) return E_INVALIDARG; + + *aColCount = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + *aColCount = mTable->ColCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_nRows(long* aRowCount) { + if (!aRowCount) return E_INVALIDARG; + + *aRowCount = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + *aRowCount = mTable->RowCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_nSelectedChildren(long* aChildCount) { + return get_nSelectedCells(aChildCount); +} + +STDMETHODIMP +ia2AccessibleTable::get_nSelectedColumns(long* aColCount) { + if (!aColCount) return E_INVALIDARG; + + *aColCount = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + *aColCount = mTable->SelectedColCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_nSelectedRows(long* aRowCount) { + if (!aRowCount) return E_INVALIDARG; + + *aRowCount = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + *aRowCount = mTable->SelectedRowCount(); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_rowDescription(long aRowIdx, BSTR* aDescription) { + if (!aDescription) return E_INVALIDARG; + + *aDescription = nullptr; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount()) + return E_INVALIDARG; + + nsAutoString descr; + mTable->RowDescription(aRowIdx, descr); + if (descr.IsEmpty()) return S_FALSE; + + *aDescription = ::SysAllocStringLen(descr.get(), descr.Length()); + return *aDescription ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleTable::get_rowExtentAt(long aRowIdx, long aColIdx, long* aSpan) { + if (!aSpan) return E_INVALIDARG; + + *aSpan = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || aColIdx < 0 || + static_cast<uint32_t>(aRowIdx) >= mTable->RowCount() || + static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + *aSpan = mTable->RowExtentAt(aRowIdx, aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_rowHeader(IAccessibleTable** aAccessibleTable, + long* aStartingColumnIndex) { + if (!aAccessibleTable || !aStartingColumnIndex) return E_INVALIDARG; + + *aAccessibleTable = nullptr; + *aStartingColumnIndex = -1; + return E_NOTIMPL; +} + +STDMETHODIMP +ia2AccessibleTable::get_rowIndex(long aCellIdx, long* aRowIdx) { + if (!aRowIdx) return E_INVALIDARG; + + *aRowIdx = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aCellIdx < 0) { + return E_INVALIDARG; + } + + long rowIdx = mTable->RowIndexAt(aCellIdx); + if (rowIdx == -1) { // Indicates an error. + return E_INVALIDARG; + } + + *aRowIdx = rowIdx; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_selectedChildren(long aMaxChildren, long** aChildren, + long* aNChildren) { + if (!aChildren || !aNChildren) return E_INVALIDARG; + + *aChildren = nullptr; + *aNChildren = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + AutoTArray<uint32_t, 30> cellIndices; + mTable->SelectedCellIndices(&cellIndices); + + uint32_t maxCells = cellIndices.Length(); + if (maxCells == 0) return S_FALSE; + + *aChildren = static_cast<LONG*>(moz_xmalloc(sizeof(LONG) * maxCells)); + *aNChildren = maxCells; + for (uint32_t i = 0; i < maxCells; i++) (*aChildren)[i] = cellIndices[i]; + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_selectedColumns(long aMaxColumns, long** aColumns, + long* aNColumns) { + return get_selectedColumns(aColumns, aNColumns); +} + +STDMETHODIMP +ia2AccessibleTable::get_selectedRows(long aMaxRows, long** aRows, + long* aNRows) { + return get_selectedRows(aRows, aNRows); +} + +STDMETHODIMP +ia2AccessibleTable::get_summary(IUnknown** aAccessible) { + if (!aAccessible) return E_INVALIDARG; + + // Neither html:table nor xul:tree nor ARIA grid/tree have an ability to + // link an accessible object to specify a summary. There is closes method + // in Table::summary to get a summary as a string which is not mapped + // directly to IAccessible2. + + *aAccessible = nullptr; + return S_FALSE; +} + +STDMETHODIMP +ia2AccessibleTable::get_isColumnSelected(long aColIdx, boolean* aIsSelected) { + if (!aIsSelected) return E_INVALIDARG; + + *aIsSelected = false; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + *aIsSelected = mTable->IsColSelected(aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_isRowSelected(long aRowIdx, boolean* aIsSelected) { + if (!aIsSelected) return E_INVALIDARG; + + *aIsSelected = false; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount()) + return E_INVALIDARG; + + *aIsSelected = mTable->IsRowSelected(aRowIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_isSelected(long aRowIdx, long aColIdx, + boolean* aIsSelected) { + if (!aIsSelected) return E_INVALIDARG; + + *aIsSelected = false; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || aColIdx < 0 || + static_cast<uint32_t>(aColIdx) >= mTable->ColCount() || + static_cast<uint32_t>(aRowIdx) >= mTable->RowCount()) + return E_INVALIDARG; + + *aIsSelected = mTable->IsCellSelected(aRowIdx, aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::selectRow(long aRowIdx) { + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount()) + return E_INVALIDARG; + + mTable->SelectRow(aRowIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::selectColumn(long aColIdx) { + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + mTable->SelectCol(aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::unselectRow(long aRowIdx) { + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount()) + return E_INVALIDARG; + + mTable->UnselectRow(aRowIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::unselectColumn(long aColIdx) { + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount()) + return E_INVALIDARG; + + mTable->UnselectCol(aColIdx); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_rowColumnExtentsAtIndex(long aCellIdx, long* aRowIdx, + long* aColIdx, + long* aRowExtents, + long* aColExtents, + boolean* aIsSelected) { + if (!aRowIdx || !aColIdx || !aRowExtents || !aColExtents || !aIsSelected) + return E_INVALIDARG; + + *aRowIdx = 0; + *aColIdx = 0; + *aRowExtents = 0; + *aColExtents = 0; + *aIsSelected = false; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + if (aCellIdx < 0) { + return E_INVALIDARG; + } + + int32_t colIdx = 0, rowIdx = 0; + mTable->RowAndColIndicesAt(aCellIdx, &rowIdx, &colIdx); + if (rowIdx == -1 || colIdx == -1) { // Indicates an error. + return E_INVALIDARG; + } + + *aRowIdx = rowIdx; + *aColIdx = colIdx; + *aRowExtents = mTable->RowExtentAt(rowIdx, colIdx); + *aColExtents = mTable->ColExtentAt(rowIdx, colIdx); + *aIsSelected = mTable->IsCellSelected(rowIdx, colIdx); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_modelChange(IA2TableModelChange* aModelChange) { + return E_NOTIMPL; +} + +//////////////////////////////////////////////////////////////////////////////// +// IAccessibleTable2 + +STDMETHODIMP +ia2AccessibleTable::get_cellAt(long aRowIdx, long aColIdx, IUnknown** aCell) { + if (!aCell) return E_INVALIDARG; + + *aCell = nullptr; + + if (!mTable) return CO_E_OBJNOTCONNECTED; + + AccessibleWrap* cell = + static_cast<AccessibleWrap*>(mTable->CellAt(aRowIdx, aColIdx)); + if (!cell) return E_INVALIDARG; + + (*aCell = static_cast<IAccessible*>(cell))->AddRef(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_nSelectedCells(long* aCellCount) { + if (!aCellCount) return E_INVALIDARG; + + *aCellCount = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + *aCellCount = mTable->SelectedCellCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_selectedCells(IUnknown*** aCells, + long* aNSelectedCells) { + if (!aCells || !aNSelectedCells) return E_INVALIDARG; + + *aCells = nullptr; + *aNSelectedCells = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + AutoTArray<Accessible*, 30> cells; + mTable->SelectedCells(&cells); + if (cells.IsEmpty()) return S_FALSE; + + *aCells = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * cells.Length())); + if (!*aCells) return E_OUTOFMEMORY; + + for (uint32_t i = 0; i < cells.Length(); i++) { + (*aCells)[i] = + static_cast<IAccessible*>(static_cast<AccessibleWrap*>(cells[i])); + ((*aCells)[i])->AddRef(); + } + + *aNSelectedCells = cells.Length(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_selectedColumns(long** aColumns, long* aNColumns) { + if (!aColumns || !aNColumns) return E_INVALIDARG; + + *aColumns = nullptr; + *aNColumns = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + AutoTArray<uint32_t, 30> colIndices; + mTable->SelectedColIndices(&colIndices); + + uint32_t maxCols = colIndices.Length(); + if (maxCols == 0) return S_FALSE; + + *aColumns = static_cast<LONG*>(moz_xmalloc(sizeof(LONG) * maxCols)); + *aNColumns = maxCols; + for (uint32_t i = 0; i < maxCols; i++) (*aColumns)[i] = colIndices[i]; + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTable::get_selectedRows(long** aRows, long* aNRows) { + if (!aRows || !aNRows) return E_INVALIDARG; + + *aRows = nullptr; + *aNRows = 0; + if (!mTable) return CO_E_OBJNOTCONNECTED; + + AutoTArray<uint32_t, 30> rowIndices; + mTable->SelectedRowIndices(&rowIndices); + + uint32_t maxRows = rowIndices.Length(); + if (maxRows == 0) return S_FALSE; + + *aRows = static_cast<LONG*>(moz_xmalloc(sizeof(LONG) * maxRows)); + *aNRows = maxRows; + for (uint32_t i = 0; i < maxRows; i++) (*aRows)[i] = rowIndices[i]; + + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleTable.h b/accessible/windows/ia2/ia2AccessibleTable.h new file mode 100644 index 0000000000..97b7103d85 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleTable.h @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_TABLE_H +#define _ACCESSIBLE_TABLE_H + +#include "AccessibleTable.h" +#include "AccessibleTable2.h" + +namespace mozilla { +namespace a11y { + +class TableAccessible; + +class ia2AccessibleTable : public IAccessibleTable, public IAccessibleTable2 { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleTable + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_accessibleAt( + /* [in] */ long row, + /* [in] */ long column, + /* [retval][out] */ IUnknown** accessible); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_caption( + /* [retval][out] */ IUnknown** accessible); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_childIndex( + /* [in] */ long rowIndex, + /* [in] */ long columnIndex, + /* [retval][out] */ long* childIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnDescription( + /* [in] */ long column, + /* [retval][out] */ BSTR* description); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnExtentAt( + /* [in] */ long row, + /* [in] */ long column, + /* [retval][out] */ long* nColumnsSpanned); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnHeader( + /* [out] */ IAccessibleTable** accessibleTable, + /* [retval][out] */ long* startingRowIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnIndex( + /* [in] */ long childIndex, + /* [retval][out] */ long* columnIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nColumns( + /* [retval][out] */ long* columnCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nRows( + /* [retval][out] */ long* rowCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedChildren( + /* [retval][out] */ long* childCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedColumns( + /* [retval][out] */ long* columnCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedRows( + /* [retval][out] */ long* rowCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowDescription( + /* [in] */ long row, + /* [retval][out] */ BSTR* description); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowExtentAt( + /* [in] */ long row, + /* [in] */ long column, + /* [retval][out] */ long* nRowsSpanned); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowHeader( + /* [out] */ IAccessibleTable** accessibleTable, + /* [retval][out] */ long* startingColumnIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowIndex( + /* [in] */ long childIndex, + /* [retval][out] */ long* rowIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedChildren( + /* [in] */ long maxChildren, + /* [length_is][length_is][size_is][size_is][out] */ long** children, + /* [retval][out] */ long* nChildren); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedColumns( + /* [in] */ long maxColumns, + /* [length_is][length_is][size_is][size_is][out] */ long** columns, + /* [retval][out] */ long* nColumns); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedRows( + /* [in] */ long maxRows, + /* [length_is][length_is][size_is][size_is][out] */ long** rows, + /* [retval][out] */ long* nRows); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_summary( + /* [retval][out] */ IUnknown** accessible); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isColumnSelected( + /* [in] */ long column, + /* [retval][out] */ boolean* isSelected); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isRowSelected( + /* [in] */ long row, + /* [retval][out] */ boolean* isSelected); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isSelected( + /* [in] */ long row, + /* [in] */ long column, + /* [retval][out] */ boolean* isSelected); + + virtual HRESULT STDMETHODCALLTYPE selectRow( + /* [in] */ long row); + + virtual HRESULT STDMETHODCALLTYPE selectColumn( + /* [in] */ long column); + + virtual HRESULT STDMETHODCALLTYPE unselectRow( + /* [in] */ long row); + + virtual HRESULT STDMETHODCALLTYPE unselectColumn( + /* [in] */ long column); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowColumnExtentsAtIndex( + /* [in] */ long index, + /* [out] */ long* row, + /* [out] */ long* column, + /* [out] */ long* rowExtents, + /* [out] */ long* columnExtents, + /* [retval][out] */ boolean* isSelected); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_modelChange( + /* [retval][out] */ IA2TableModelChange* modelChange); + + // IAccessibleTable2 + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_cellAt( + /* [in] */ long row, + /* [in] */ long column, + /* [out, retval] */ IUnknown** cell); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedCells( + /* [out, retval] */ long* cellCount); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedCells( + /* [out, size_is(,*nSelectedCells,)] */ IUnknown*** cells, + /* [out, retval] */ long* nSelectedCells); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedColumns( + /* [out, size_is(,*nColumns)] */ long** selectedColumns, + /* [out, retval] */ long* nColumns); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedRows( + /* [out, size_is(,*nRows)] */ long** selectedRows, + /* [out, retval] */ long* nRows); + + protected: + ia2AccessibleTable(TableAccessible* aTable) : mTable(aTable) {} + + TableAccessible* mTable; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.cpp b/accessible/windows/ia2/ia2AccessibleTableCell.cpp new file mode 100644 index 0000000000..c1589d0acd --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleTableCell.cpp @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "AccessibleTable2_i.c" +#include "AccessibleTableCell_i.c" + +#include "AccessibleWrap.h" +#include "TableAccessible.h" +#include "TableCellAccessible.h" +#include "IUnknownImpl.h" + +#include "nsCOMPtr.h" +#include "nsString.h" + +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleTableCell::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleTableCell == iid) { + *ppv = static_cast<IAccessibleTableCell*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//////////////////////////////////////////////////////////////////////////////// +// IAccessibleTableCell + +STDMETHODIMP +ia2AccessibleTableCell::get_table(IUnknown** aTable) { + if (!aTable) return E_INVALIDARG; + + *aTable = nullptr; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + TableAccessible* table = mTableCell->Table(); + if (!table) return E_FAIL; + + AccessibleWrap* wrap = static_cast<AccessibleWrap*>(table->AsAccessible()); + *aTable = static_cast<IAccessible*>(wrap); + (*aTable)->AddRef(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_columnExtent(long* aSpan) { + if (!aSpan) return E_INVALIDARG; + + *aSpan = 0; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + *aSpan = mTableCell->ColExtent(); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_columnHeaderCells(IUnknown*** aCellAccessibles, + long* aNColumnHeaderCells) { + if (!aCellAccessibles || !aNColumnHeaderCells) return E_INVALIDARG; + + *aCellAccessibles = nullptr; + *aNColumnHeaderCells = 0; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + AutoTArray<Accessible*, 10> cells; + mTableCell->ColHeaderCells(&cells); + + *aNColumnHeaderCells = cells.Length(); + *aCellAccessibles = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * cells.Length())); + + if (!*aCellAccessibles) return E_OUTOFMEMORY; + + for (uint32_t i = 0; i < cells.Length(); i++) { + AccessibleWrap* cell = static_cast<AccessibleWrap*>(cells[i]); + (*aCellAccessibles)[i] = static_cast<IAccessible*>(cell); + (*aCellAccessibles)[i]->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_columnIndex(long* aColIdx) { + if (!aColIdx) return E_INVALIDARG; + + *aColIdx = -1; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + *aColIdx = mTableCell->ColIdx(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_rowExtent(long* aSpan) { + if (!aSpan) return E_INVALIDARG; + + *aSpan = 0; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + *aSpan = mTableCell->RowExtent(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_rowHeaderCells(IUnknown*** aCellAccessibles, + long* aNRowHeaderCells) { + if (!aCellAccessibles || !aNRowHeaderCells) return E_INVALIDARG; + + *aCellAccessibles = nullptr; + *aNRowHeaderCells = 0; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + AutoTArray<Accessible*, 10> cells; + mTableCell->RowHeaderCells(&cells); + + *aNRowHeaderCells = cells.Length(); + *aCellAccessibles = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * cells.Length())); + if (!*aCellAccessibles) return E_OUTOFMEMORY; + + for (uint32_t i = 0; i < cells.Length(); i++) { + AccessibleWrap* cell = static_cast<AccessibleWrap*>(cells[i]); + (*aCellAccessibles)[i] = static_cast<IAccessible*>(cell); + (*aCellAccessibles)[i]->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_rowIndex(long* aRowIdx) { + if (!aRowIdx) return E_INVALIDARG; + + *aRowIdx = -1; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + *aRowIdx = mTableCell->RowIdx(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_rowColumnExtents(long* aRowIdx, long* aColIdx, + long* aRowExtents, + long* aColExtents, + boolean* aIsSelected) { + if (!aRowIdx || !aColIdx || !aRowExtents || !aColExtents || !aIsSelected) + return E_INVALIDARG; + + *aRowIdx = *aColIdx = *aRowExtents = *aColExtents = 0; + *aIsSelected = false; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + *aRowIdx = mTableCell->RowIdx(); + *aColIdx = mTableCell->ColIdx(); + *aRowExtents = mTableCell->RowExtent(); + *aColExtents = mTableCell->ColExtent(); + *aIsSelected = mTableCell->Selected(); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleTableCell::get_isSelected(boolean* aIsSelected) { + if (!aIsSelected) return E_INVALIDARG; + + *aIsSelected = false; + if (!mTableCell) return CO_E_OBJNOTCONNECTED; + + *aIsSelected = mTableCell->Selected(); + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.h b/accessible/windows/ia2/ia2AccessibleTableCell.h new file mode 100644 index 0000000000..bb7504f20c --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleTableCell.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_TABLECELL_H +#define _ACCESSIBLE_TABLECELL_H + +#include "AccessibleTableCell.h" + +namespace mozilla { +namespace a11y { +class TableCellAccessible; + +class ia2AccessibleTableCell : public IAccessibleTableCell { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleTableCell + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_table( + /* [out, retval] */ IUnknown** table); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnExtent( + /* [out, retval] */ long* nColumnsSpanned); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnHeaderCells( + /* [out, size_is(,*nColumnHeaderCells,)] */ IUnknown*** cellAccessibles, + /* [out, retval] */ long* nColumnHeaderCells); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnIndex( + /* [out, retval] */ long* columnIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowExtent( + /* [out, retval] */ long* nRowsSpanned); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowHeaderCells( + /* [out, size_is(,*nRowHeaderCells,)] */ IUnknown*** cellAccessibles, + /* [out, retval] */ long* nRowHeaderCells); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowIndex( + /* [out, retval] */ long* rowIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowColumnExtents( + /* [out] */ long* row, + /* [out] */ long* column, + /* [out] */ long* rowExtents, + /* [out] */ long* columnExtents, + /* [out, retval] */ boolean* isSelected); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isSelected( + /* [out, retval] */ boolean* isSelected); + + protected: + ia2AccessibleTableCell(TableCellAccessible* aTableCell) + : mTableCell(aTableCell) {} + + TableCellAccessible* mTableCell; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleText.cpp b/accessible/windows/ia2/ia2AccessibleText.cpp new file mode 100644 index 0000000000..85cd7113f2 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleText.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleText.h" + +#include "AccessibleText_i.c" + +#include "HyperTextAccessibleWrap.h" +#include "HyperTextAccessible-inl.h" +#include "ProxyWrappers.h" +#include "mozilla/ClearOnShutdown.h" + +using namespace mozilla::a11y; + +StaticRefPtr<HyperTextAccessibleWrap> ia2AccessibleText::sLastTextChangeAcc; +StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString; +uint32_t ia2AccessibleText::sLastTextChangeStart = 0; +uint32_t ia2AccessibleText::sLastTextChangeEnd = 0; +bool ia2AccessibleText::sLastTextChangeWasInsert = false; + +// IAccessibleText + +STDMETHODIMP +ia2AccessibleText::addSelection(long aStartOffset, long aEndOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + return textAcc->AddToSelection(aStartOffset, aEndOffset) ? S_OK + : E_INVALIDARG; +} + +STDMETHODIMP +ia2AccessibleText::get_attributes(long aOffset, long* aStartOffset, + long* aEndOffset, BSTR* aTextAttributes) { + if (!aStartOffset || !aEndOffset || !aTextAttributes) return E_INVALIDARG; + + *aStartOffset = 0; + *aEndOffset = 0; + *aTextAttributes = nullptr; + + int32_t startOffset = 0, endOffset = 0; + HRESULT hr; + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + nsCOMPtr<nsIPersistentProperties> attributes = + textAcc->TextAttributes(true, aOffset, &startOffset, &endOffset); + + hr = AccessibleWrap::ConvertToIA2Attributes(attributes, aTextAttributes); + if (FAILED(hr)) return hr; + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::get_caretOffset(long* aOffset) { + if (!aOffset) return E_INVALIDARG; + + *aOffset = -1; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + *aOffset = textAcc->CaretOffset(); + + return *aOffset != -1 ? S_OK : S_FALSE; +} + +STDMETHODIMP +ia2AccessibleText::get_characterExtents(long aOffset, + enum IA2CoordinateType aCoordType, + long* aX, long* aY, long* aWidth, + long* aHeight) { + if (!aX || !aY || !aWidth || !aHeight) return E_INVALIDARG; + *aX = *aY = *aWidth = *aHeight = 0; + + uint32_t geckoCoordType = + (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) + ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; + nsIntRect rect; + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + rect = textAcc->CharBounds(aOffset, geckoCoordType); + + // Can't use GetRect() because of long vs. int32_t mismatch + *aX = rect.X(); + *aY = rect.Y(); + *aWidth = rect.Width(); + *aHeight = rect.Height(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::get_nSelections(long* aNSelections) { + if (!aNSelections) return E_INVALIDARG; + *aNSelections = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + *aNSelections = textAcc->SelectionCount(); + + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::get_offsetAtPoint(long aX, long aY, + enum IA2CoordinateType aCoordType, + long* aOffset) { + if (!aOffset) return E_INVALIDARG; + *aOffset = 0; + + uint32_t geckoCoordType = + (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) + ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + *aOffset = textAcc->OffsetAtPoint(aX, aY, geckoCoordType); + + return *aOffset == -1 ? S_FALSE : S_OK; +} + +STDMETHODIMP +ia2AccessibleText::get_selection(long aSelectionIndex, long* aStartOffset, + long* aEndOffset) { + if (!aStartOffset || !aEndOffset) return E_INVALIDARG; + *aStartOffset = *aEndOffset = 0; + + int32_t startOffset = 0, endOffset = 0; + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + if (!textAcc->SelectionBoundsAt(aSelectionIndex, &startOffset, &endOffset)) { + return E_INVALIDARG; + } + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::get_text(long aStartOffset, long aEndOffset, BSTR* aText) { + if (!aText) return E_INVALIDARG; + + *aText = nullptr; + + nsAutoString text; + MOZ_ASSERT(!HyperTextProxyFor(this)); + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) { + return E_INVALIDARG; + } + + textAcc->TextSubstring(aStartOffset, aEndOffset, text); + + if (text.IsEmpty()) return S_FALSE; + + *aText = ::SysAllocStringLen(text.get(), text.Length()); + return *aText ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleText::get_textBeforeOffset(long aOffset, + enum IA2TextBoundaryType aBoundaryType, + long* aStartOffset, long* aEndOffset, + BSTR* aText) { + if (!aStartOffset || !aEndOffset || !aText) return E_INVALIDARG; + + *aStartOffset = *aEndOffset = 0; + *aText = nullptr; + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; + + nsAutoString text; + int32_t startOffset = 0, endOffset = 0; + + if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) { + startOffset = 0; + endOffset = textAcc->CharacterCount(); + textAcc->TextSubstring(startOffset, endOffset, text); + } else { + AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType); + if (boundaryType == -1) return S_FALSE; + + textAcc->TextBeforeOffset(aOffset, boundaryType, &startOffset, &endOffset, + text); + } + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + if (text.IsEmpty()) return S_FALSE; + + *aText = ::SysAllocStringLen(text.get(), text.Length()); + return *aText ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleText::get_textAfterOffset(long aOffset, + enum IA2TextBoundaryType aBoundaryType, + long* aStartOffset, long* aEndOffset, + BSTR* aText) { + if (!aStartOffset || !aEndOffset || !aText) return E_INVALIDARG; + + *aStartOffset = 0; + *aEndOffset = 0; + *aText = nullptr; + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; + + nsAutoString text; + int32_t startOffset = 0, endOffset = 0; + + if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) { + startOffset = 0; + endOffset = textAcc->CharacterCount(); + textAcc->TextSubstring(startOffset, endOffset, text); + } else { + AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType); + if (boundaryType == -1) return S_FALSE; + textAcc->TextAfterOffset(aOffset, boundaryType, &startOffset, &endOffset, + text); + } + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + if (text.IsEmpty()) return S_FALSE; + + *aText = ::SysAllocStringLen(text.get(), text.Length()); + return *aText ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleText::get_textAtOffset(long aOffset, + enum IA2TextBoundaryType aBoundaryType, + long* aStartOffset, long* aEndOffset, + BSTR* aText) { + if (!aStartOffset || !aEndOffset || !aText) return E_INVALIDARG; + + *aStartOffset = *aEndOffset = 0; + *aText = nullptr; + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; + + nsAutoString text; + int32_t startOffset = 0, endOffset = 0; + if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) { + startOffset = 0; + endOffset = textAcc->CharacterCount(); + textAcc->TextSubstring(startOffset, endOffset, text); + } else { + AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType); + if (boundaryType == -1) return S_FALSE; + textAcc->TextAtOffset(aOffset, boundaryType, &startOffset, &endOffset, + text); + } + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + if (text.IsEmpty()) return S_FALSE; + + *aText = ::SysAllocStringLen(text.get(), text.Length()); + return *aText ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ia2AccessibleText::removeSelection(long aSelectionIndex) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + return textAcc->RemoveFromSelection(aSelectionIndex) ? S_OK : E_INVALIDARG; +} + +STDMETHODIMP +ia2AccessibleText::setCaretOffset(long aOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidOffset(aOffset)) return E_INVALIDARG; + + textAcc->SetCaretOffset(aOffset); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::setSelection(long aSelectionIndex, long aStartOffset, + long aEndOffset) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + return textAcc->SetSelectionBoundsAt(aSelectionIndex, aStartOffset, + aEndOffset) + ? S_OK + : E_INVALIDARG; +} + +STDMETHODIMP +ia2AccessibleText::get_nCharacters(long* aNCharacters) { + if (!aNCharacters) return E_INVALIDARG; + *aNCharacters = 0; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aNCharacters = textAcc->CharacterCount(); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::scrollSubstringTo(long aStartIndex, long aEndIndex, + enum IA2ScrollType aScrollType) { + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidRange(aStartIndex, aEndIndex)) return E_INVALIDARG; + + textAcc->ScrollSubstringTo(aStartIndex, aEndIndex, aScrollType); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::scrollSubstringToPoint(long aStartIndex, long aEndIndex, + enum IA2CoordinateType aCoordType, + long aX, long aY) { + uint32_t geckoCoordType = + (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) + ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE; + + MOZ_ASSERT(!HyperTextProxyFor(this)); + + HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); + if (textAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!textAcc->IsValidRange(aStartIndex, aEndIndex)) return E_INVALIDARG; + + textAcc->ScrollSubstringToPoint(aStartIndex, aEndIndex, geckoCoordType, aX, + aY); + return S_OK; +} + +STDMETHODIMP +ia2AccessibleText::get_newText(IA2TextSegment* aNewText) { + return GetModifiedText(true, aNewText); +} + +STDMETHODIMP +ia2AccessibleText::get_oldText(IA2TextSegment* aOldText) { + return GetModifiedText(false, aOldText); +} + +// ia2AccessibleText + +HRESULT +ia2AccessibleText::GetModifiedText(bool aGetInsertedText, + IA2TextSegment* aText) { + if (!aText) return E_INVALIDARG; + + if (!sLastTextChangeAcc) return S_OK; + + if (aGetInsertedText != sLastTextChangeWasInsert) return S_OK; + + if (sLastTextChangeAcc != this) return S_OK; + + aText->start = sLastTextChangeStart; + aText->end = sLastTextChangeEnd; + + if (sLastTextChangeString->IsEmpty()) return S_FALSE; + + aText->text = ::SysAllocStringLen(sLastTextChangeString->get(), + sLastTextChangeString->Length()); + return aText->text ? S_OK : E_OUTOFMEMORY; +} + +AccessibleTextBoundary ia2AccessibleText::GetGeckoTextBoundary( + enum IA2TextBoundaryType aBoundaryType) { + switch (aBoundaryType) { + case IA2_TEXT_BOUNDARY_CHAR: + return nsIAccessibleText::BOUNDARY_CHAR; + case IA2_TEXT_BOUNDARY_WORD: + return nsIAccessibleText::BOUNDARY_WORD_START; + case IA2_TEXT_BOUNDARY_LINE: + return nsIAccessibleText::BOUNDARY_LINE_START; + case IA2_TEXT_BOUNDARY_PARAGRAPH: + return nsIAccessibleText::BOUNDARY_PARAGRAPH; + // case IA2_TEXT_BOUNDARY_SENTENCE: + // XXX: not implemented + default: + return -1; + } +} + +void ia2AccessibleText::InitTextChangeData() { + ClearOnShutdown(&sLastTextChangeAcc); + ClearOnShutdown(&sLastTextChangeString); +} + +void ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleWrap* aAcc, + bool aInsert, const nsString& aStr, + int32_t aStart, uint32_t aLen) { + if (!sLastTextChangeString) sLastTextChangeString = new nsString(); + + sLastTextChangeAcc = aAcc; + sLastTextChangeStart = aStart; + sLastTextChangeEnd = aStart + aLen; + sLastTextChangeWasInsert = aInsert; + *sLastTextChangeString = aStr; +} diff --git a/accessible/windows/ia2/ia2AccessibleText.h b/accessible/windows/ia2/ia2AccessibleText.h new file mode 100644 index 0000000000..cd4f1ad5a7 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleText.h @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_TEXT_H +#define _ACCESSIBLE_TEXT_H + +#include "AccessibleText.h" + +namespace mozilla { +namespace a11y { +class HyperTextAccessibleWrap; + +class ia2AccessibleText : public IAccessibleText { + public: + // IAccessibleText + virtual HRESULT STDMETHODCALLTYPE addSelection( + /* [in] */ long startOffset, + /* [in] */ long endOffset); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_attributes( + /* [in] */ long offset, + /* [out] */ long* startOffset, + /* [out] */ long* endOffset, + /* [retval][out] */ BSTR* textAttributes); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_caretOffset( + /* [retval][out] */ long* offset); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_characterExtents( + /* [in] */ long offset, + /* [in] */ enum IA2CoordinateType coordType, + /* [out] */ long* x, + /* [out] */ long* y, + /* [out] */ long* width, + /* [retval][out] */ long* height); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelections( + /* [retval][out] */ long* nSelections); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_offsetAtPoint( + /* [in] */ long x, + /* [in] */ long y, + /* [in] */ enum IA2CoordinateType coordType, + /* [retval][out] */ long* offset); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selection( + /* [in] */ long selectionIndex, + /* [out] */ long* startOffset, + /* [retval][out] */ long* endOffset); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_text( + /* [in] */ long startOffset, + /* [in] */ long endOffset, + /* [retval][out] */ BSTR* text); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_textBeforeOffset( + /* [in] */ long offset, + /* [in] */ enum IA2TextBoundaryType boundaryType, + /* [out] */ long* startOffset, + /* [out] */ long* endOffset, + /* [retval][out] */ BSTR* text); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_textAfterOffset( + /* [in] */ long offset, + /* [in] */ enum IA2TextBoundaryType boundaryType, + /* [out] */ long* startOffset, + /* [out] */ long* endOffset, + /* [retval][out] */ BSTR* text); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_textAtOffset( + /* [in] */ long offset, + /* [in] */ enum IA2TextBoundaryType boundaryType, + /* [out] */ long* startOffset, + /* [out] */ long* endOffset, + /* [retval][out] */ BSTR* text); + + virtual HRESULT STDMETHODCALLTYPE removeSelection( + /* [in] */ long selectionIndex); + + virtual HRESULT STDMETHODCALLTYPE setCaretOffset( + /* [in] */ long offset); + + virtual HRESULT STDMETHODCALLTYPE setSelection( + /* [in] */ long selectionIndex, + /* [in] */ long startOffset, + /* [in] */ long endOffset); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nCharacters( + /* [retval][out] */ long* nCharacters); + + virtual HRESULT STDMETHODCALLTYPE scrollSubstringTo( + /* [in] */ long startIndex, + /* [in] */ long endIndex, + /* [in] */ enum IA2ScrollType scrollType); + + virtual HRESULT STDMETHODCALLTYPE scrollSubstringToPoint( + /* [in] */ long startIndex, + /* [in] */ long endIndex, + /* [in] */ enum IA2CoordinateType coordinateType, + /* [in] */ long x, + /* [in] */ long y); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_newText( + /* [retval][out] */ IA2TextSegment* newText); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_oldText( + /* [retval][out] */ IA2TextSegment* oldText); + + static void InitTextChangeData(); + static void UpdateTextChangeData(HyperTextAccessibleWrap* aAcc, bool aInsert, + const nsString& aStr, int32_t aStart, + uint32_t aLen); + + protected: + static StaticRefPtr<HyperTextAccessibleWrap> sLastTextChangeAcc; + static StaticAutoPtr<nsString> sLastTextChangeString; + static bool sLastTextChangeWasInsert; + static uint32_t sLastTextChangeStart; + static uint32_t sLastTextChangeEnd; + + private: + HRESULT GetModifiedText(bool aGetInsertedText, IA2TextSegment* aNewText); + AccessibleTextBoundary GetGeckoTextBoundary( + enum IA2TextBoundaryType coordinateType); +}; + +} // namespace a11y +} // namespace mozilla + +#define FORWARD_IACCESSIBLETEXT(Class) \ + virtual HRESULT STDMETHODCALLTYPE addSelection(long startOffset, \ + long endOffset) { \ + return Class::addSelection(startOffset, endOffset); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_attributes( \ + long offset, long* startOffset, long* endOffset, BSTR* textAttributes) { \ + return Class::get_attributes(offset, startOffset, endOffset, \ + textAttributes); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_caretOffset(long* offset) { \ + return Class::get_caretOffset(offset); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_characterExtents( \ + long offset, enum IA2CoordinateType coordType, long* x, long* y, \ + long* width, long* height) { \ + return Class::get_characterExtents(offset, coordType, x, y, width, \ + height); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_nSelections(long* nSelections) { \ + return Class::get_nSelections(nSelections); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_offsetAtPoint( \ + long x, long y, enum IA2CoordinateType coordType, long* offset) { \ + return Class::get_offsetAtPoint(x, y, coordType, offset); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_selection( \ + long selectionIndex, long* startOffset, long* endOffset) { \ + return Class::get_selection(selectionIndex, startOffset, endOffset); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_text(long startOffset, long endOffset, \ + BSTR* text) { \ + return Class::get_text(startOffset, endOffset, text); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_textBeforeOffset( \ + long offset, enum IA2TextBoundaryType boundaryType, long* startOffset, \ + long* endOffset, BSTR* text) { \ + return Class::get_textBeforeOffset(offset, boundaryType, startOffset, \ + endOffset, text); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_textAfterOffset( \ + long offset, enum IA2TextBoundaryType boundaryType, long* startOffset, \ + long* endOffset, BSTR* text) { \ + return Class::get_textAfterOffset(offset, boundaryType, startOffset, \ + endOffset, text); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_textAtOffset( \ + long offset, enum IA2TextBoundaryType boundaryType, long* startOffset, \ + long* endOffset, BSTR* text) { \ + return Class::get_textAtOffset(offset, boundaryType, startOffset, \ + endOffset, text); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE removeSelection(long selectionIndex) { \ + return Class::removeSelection(selectionIndex); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE setCaretOffset(long offset) { \ + return Class::setCaretOffset(offset); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE setSelection( \ + long selectionIndex, long startOffset, long endOffset) { \ + return Class::setSelection(selectionIndex, startOffset, endOffset); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_nCharacters(long* nCharacters) { \ + return Class::get_nCharacters(nCharacters); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE scrollSubstringTo( \ + long startIndex, long endIndex, enum IA2ScrollType scrollType) { \ + return Class::scrollSubstringTo(startIndex, endIndex, scrollType); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE scrollSubstringToPoint( \ + long startIndex, long endIndex, enum IA2CoordinateType coordinateType, \ + long x, long y) { \ + return Class::scrollSubstringToPoint(startIndex, endIndex, coordinateType, \ + x, y); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_newText(IA2TextSegment* newText) { \ + return Class::get_newText(newText); \ + } \ + \ + virtual HRESULT STDMETHODCALLTYPE get_oldText(IA2TextSegment* oldText) { \ + return Class::get_oldText(oldText); \ + } + +#endif diff --git a/accessible/windows/ia2/ia2AccessibleValue.cpp b/accessible/windows/ia2/ia2AccessibleValue.cpp new file mode 100644 index 0000000000..7c41ba5758 --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleValue.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ia2AccessibleValue.h" + +#include "AccessibleValue_i.c" + +#include "AccessibleWrap.h" +#include "Accessible-inl.h" +#include "IUnknownImpl.h" + +#include "mozilla/FloatingPoint.h" + +using namespace mozilla::a11y; + +// IUnknown + +STDMETHODIMP +ia2AccessibleValue::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleValue == iid) { + AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this); + if (valueAcc->HasNumericValue()) { + *ppv = static_cast<IAccessibleValue*>(this); + valueAcc->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + return E_NOINTERFACE; +} + +// IAccessibleValue + +STDMETHODIMP +ia2AccessibleValue::get_currentValue(VARIANT* aCurrentValue) { + if (!aCurrentValue) return E_INVALIDARG; + + VariantInit(aCurrentValue); + + AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this); + double currentValue; + MOZ_ASSERT(!valueAcc->IsProxy()); + if (valueAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + currentValue = valueAcc->CurValue(); + + if (IsNaN(currentValue)) return S_FALSE; + + aCurrentValue->vt = VT_R8; + aCurrentValue->dblVal = currentValue; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleValue::setCurrentValue(VARIANT aValue) { + if (aValue.vt != VT_R8) return E_INVALIDARG; + + AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this); + MOZ_ASSERT(!valueAcc->IsProxy()); + + if (valueAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + return valueAcc->SetCurValue(aValue.dblVal) ? S_OK : E_FAIL; +} + +STDMETHODIMP +ia2AccessibleValue::get_maximumValue(VARIANT* aMaximumValue) { + if (!aMaximumValue) return E_INVALIDARG; + + VariantInit(aMaximumValue); + + AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this); + double maximumValue; + MOZ_ASSERT(!valueAcc->IsProxy()); + if (valueAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + maximumValue = valueAcc->MaxValue(); + + if (IsNaN(maximumValue)) return S_FALSE; + + aMaximumValue->vt = VT_R8; + aMaximumValue->dblVal = maximumValue; + return S_OK; +} + +STDMETHODIMP +ia2AccessibleValue::get_minimumValue(VARIANT* aMinimumValue) { + if (!aMinimumValue) return E_INVALIDARG; + + VariantInit(aMinimumValue); + + AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this); + double minimumValue; + MOZ_ASSERT(!valueAcc->IsProxy()); + if (valueAcc->IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + minimumValue = valueAcc->MinValue(); + + if (IsNaN(minimumValue)) return S_FALSE; + + aMinimumValue->vt = VT_R8; + aMinimumValue->dblVal = minimumValue; + return S_OK; +} diff --git a/accessible/windows/ia2/ia2AccessibleValue.h b/accessible/windows/ia2/ia2AccessibleValue.h new file mode 100644 index 0000000000..e430c60e5a --- /dev/null +++ b/accessible/windows/ia2/ia2AccessibleValue.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 _ACCESSIBLE_VALUE_H +#define _ACCESSIBLE_VALUE_H + +#include "AccessibleValue.h" + +namespace mozilla { +namespace a11y { + +class ia2AccessibleValue : public IAccessibleValue { + public: + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleValue + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_currentValue( + /* [retval][out] */ VARIANT* currentValue); + + virtual HRESULT STDMETHODCALLTYPE setCurrentValue( + /* [in] */ VARIANT value); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_maximumValue( + /* [retval][out] */ VARIANT* maximumValue); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_minimumValue( + /* [retval][out] */ VARIANT* minimumValue); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/ia2/moz.build b/accessible/windows/ia2/moz.build new file mode 100644 index 0000000000..7357fe48a8 --- /dev/null +++ b/accessible/windows/ia2/moz.build @@ -0,0 +1,58 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "ia2Accessible.h", + "ia2AccessibleAction.h", + "ia2AccessibleComponent.h", + "ia2AccessibleEditableText.h", + "ia2AccessibleHyperlink.h", + "ia2AccessibleHypertext.h", + "ia2AccessibleText.h", + "ia2AccessibleValue.h", +] + +UNIFIED_SOURCES += [ + "ia2Accessible.cpp", + "ia2AccessibleAction.cpp", + "ia2AccessibleComponent.cpp", + "ia2AccessibleEditableText.cpp", + "ia2AccessibleHyperlink.cpp", + "ia2AccessibleHypertext.cpp", + "ia2AccessibleImage.cpp", + "ia2AccessibleRelation.cpp", + "ia2AccessibleText.cpp", + "ia2AccessibleValue.cpp", +] + +# These files cannot be built in unified mode because they both include +# AccessibleTable2_i.c. +SOURCES += [ + "ia2AccessibleTable.cpp", + "ia2AccessibleTableCell.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/windows", + "/accessible/windows/msaa", + "/accessible/xpcom", + "/accessible/xul", +] + +FINAL_LIBRARY = "xul" + +# The Windows MIDL code generator creates things like: +# +# #endif !_MIDL_USE_GUIDDEF_ +# +# which clang-cl complains about. MSVC doesn't, so turn this warning off. +if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += ["-Wno-extra-tokens"] + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/accessible/windows/moz.build b/accessible/windows/moz.build new file mode 100644 index 0000000000..c5c2f9ff2f --- /dev/null +++ b/accessible/windows/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += ["msaa", "ia2", "sdn", "uia"] + +EXPORTS.mozilla.a11y += [ + "ProxyWrappers.h", +] diff --git a/accessible/windows/msaa/ARIAGridAccessibleWrap.cpp b/accessible/windows/msaa/ARIAGridAccessibleWrap.cpp new file mode 100644 index 0000000000..865dd87c72 --- /dev/null +++ b/accessible/windows/msaa/ARIAGridAccessibleWrap.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ARIAGridAccessibleWrap.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// ARIAGridAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessibleWrap, ARIAGridAccessible) + +IMPL_IUNKNOWN_INHERITED2(ARIAGridAccessibleWrap, AccessibleWrap, + HyperTextAccessibleWrap, ia2AccessibleTable) + +void ARIAGridAccessibleWrap::Shutdown() { + ia2AccessibleTable::mTable = nullptr; + ARIAGridAccessible::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ARIAGridCellAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessibleWrap, ARIAGridCellAccessible) + +IMPL_IUNKNOWN_INHERITED1(ARIAGridCellAccessibleWrap, HyperTextAccessibleWrap, + ia2AccessibleTableCell) + +void ARIAGridCellAccessibleWrap::Shutdown() { + ia2AccessibleTableCell::mTableCell = nullptr; + ARIAGridCellAccessible::Shutdown(); +} diff --git a/accessible/windows/msaa/ARIAGridAccessibleWrap.h b/accessible/windows/msaa/ARIAGridAccessibleWrap.h new file mode 100644 index 0000000000..cb6f174804 --- /dev/null +++ b/accessible/windows/msaa/ARIAGridAccessibleWrap.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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_ARIAGRIDACCESSIBLEWRAP_H +#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H + +#include "ARIAGridAccessible.h" +#include "ia2AccessibleTable.h" +#include "ia2AccessibleTableCell.h" + +namespace mozilla { +namespace a11y { + +/** + * IA2 wrapper class for ARIAGridAccessible implementing IAccessibleTable and + * IAccessibleTable2 interfaces. + */ +class ARIAGridAccessibleWrap : public ARIAGridAccessible, + public ia2AccessibleTable { + ~ARIAGridAccessibleWrap() {} + + public: + ARIAGridAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : ARIAGridAccessible(aContent, aDoc), ia2AccessibleTable(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + // Need to declare addref/release here unconditionally, because + // ia2AccessibleTable has pure-virtual refcounting. + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +/** + * IA2 wrapper class for ARIAGridCellAccessible implementing + * IAccessibleTableCell interface. + */ +class ARIAGridCellAccessibleWrap : public ARIAGridCellAccessible, + public ia2AccessibleTableCell { + ~ARIAGridCellAccessibleWrap() {} + + public: + ARIAGridCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : ARIAGridCellAccessible(aContent, aDoc), ia2AccessibleTableCell(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + // Need to declare addref/release here unconditionally, because + // ia2AccessibleTable has pure-virtual refcounting. + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/AccessibleWrap.cpp b/accessible/windows/msaa/AccessibleWrap.cpp new file mode 100644 index 0000000000..6c9afbe642 --- /dev/null +++ b/accessible/windows/msaa/AccessibleWrap.cpp @@ -0,0 +1,1773 @@ +/* -*- 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 "AccessibleWrap.h" +#include "Accessible-inl.h" + +#include "Compatibility.h" +#include "DocAccessible-inl.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "EnumVariant.h" +#include "GeckoCustom.h" +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "nsIAccessibleEvent.h" +#include "nsWindowsHelpers.h" +#include "nsWinUtils.h" +#include "mozilla/a11y/ProxyAccessible.h" +#include "ProxyWrappers.h" +#include "ServiceProvider.h" +#include "Relation.h" +#include "Role.h" +#include "RootAccessible.h" +#include "sdnAccessible.h" +#include "States.h" + +#ifdef A11Y_LOG +# include "Logging.h" +#endif + +#include "nsIFrame.h" +#include "nsIScrollableFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/dom/NodeInfo.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "nsNameSpaceManager.h" +#include "nsTextFormatter.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsEventMap.h" +#include "nsArrayUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/ReverseIterator.h" +#include "mozilla/mscom/AsyncInvoker.h" +#include "mozilla/mscom/Interceptor.h" + +#include "oleacc.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +const uint32_t USE_ROLE_STRING = 0; + +/* For documentation of the accessibility architecture, + * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html + */ + +//#define DEBUG_LEAKS + +#ifdef DEBUG_LEAKS +static gAccessibles = 0; +#endif + +MsaaIdGenerator AccessibleWrap::sIDGen; +StaticAutoPtr<nsTArray<AccessibleWrap::HandlerControllerData>> + AccessibleWrap::sHandlerControllers; + +static const VARIANT kVarChildIdSelf = {{{VT_I4}}}; + +static const int32_t kIEnumVariantDisconnected = -1; + +//////////////////////////////////////////////////////////////////////////////// +// AccessibleWrap +//////////////////////////////////////////////////////////////////////////////// +AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : Accessible(aContent, aDoc), mID(kNoID) {} + +AccessibleWrap::~AccessibleWrap() { + if (mID != kNoID) { + sIDGen.ReleaseID(WrapNotNull(this)); + } +} + +ITypeInfo* AccessibleWrap::gTypeInfo = nullptr; + +NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible) + +void AccessibleWrap::Shutdown() { + if (mID != kNoID) { + auto doc = static_cast<DocAccessibleWrap*>(mDoc.get()); + // Accessibles can be shut down twice in some cases. When this happens, + // doc will be null. + if (doc) { + doc->RemoveID(mID); + } + } + + if (XRE_IsContentProcess()) { + // Bug 1434822: To improve performance for cross-process COM, we disable COM + // garbage collection. However, this means we never receive Release calls + // from clients, so defunct accessibles can never be deleted. Since we + // know when an accessible is shutting down, we can work around this by + // forcing COM to disconnect this object from all of its remote clients, + // which will cause associated references to be released. + IUnknown* unk = static_cast<IAccessible*>(this); + mscom::Interceptor::DisconnectRemotesForTarget(unk); + // If an accessible was retrieved via IAccessibleHypertext::hyperlink*, + // it will have a different Interceptor that won't be matched by the above + // call, even though it's the same object. Therefore, call it again with + // the IAccessibleHyperlink pointer. We can remove this horrible hack once + // bug 1440267 is fixed. + unk = static_cast<IAccessibleHyperlink*>(this); + mscom::Interceptor::DisconnectRemotesForTarget(unk); + for (auto& assocUnk : mAssociatedCOMObjectsForDisconnection) { + mscom::Interceptor::DisconnectRemotesForTarget(assocUnk); + } + mAssociatedCOMObjectsForDisconnection.Clear(); + } + + Accessible::Shutdown(); +} + +//----------------------------------------------------- +// IUnknown interface methods - see iunknown.h for documentation +//----------------------------------------------------- + +// Microsoft COM QueryInterface +STDMETHODIMP +AccessibleWrap::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IClientSecurity == iid) { + // Some code might QI(IID_IClientSecurity) to detect whether or not we are + // a proxy. Right now that can potentially happen off the main thread, so we + // look for this condition immediately so that we don't trigger other code + // that might not be thread-safe. + return E_NOINTERFACE; + } + + if (IID_IUnknown == iid) + *ppv = static_cast<IAccessible*>(this); + else if (IID_IDispatch == iid || IID_IAccessible == iid) + *ppv = static_cast<IAccessible*>(this); + else if (IID_IEnumVARIANT == iid && !IsProxy()) { + // Don't support this interface for leaf elements. + if (!HasChildren() || nsAccUtils::MustPrune(this)) return E_NOINTERFACE; + + *ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this)); + } else if (IID_IServiceProvider == iid) + *ppv = new ServiceProvider(this); + else if (IID_ISimpleDOMNode == iid && !IsProxy()) { + if (IsDefunct() || (!HasOwnContent() && !IsDoc())) return E_NOINTERFACE; + + *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(WrapNotNull(this))); + } + + if (nullptr == *ppv) { + HRESULT hr = ia2Accessible::QueryInterface(iid, ppv); + if (SUCCEEDED(hr)) return hr; + } + + if (nullptr == *ppv && !IsProxy()) { + HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv); + if (SUCCEEDED(hr)) return hr; + } + + if (nullptr == *ppv) { + HRESULT hr = ia2AccessibleHyperlink::QueryInterface(iid, ppv); + if (SUCCEEDED(hr)) return hr; + } + + if (nullptr == *ppv && !IsProxy()) { + HRESULT hr = ia2AccessibleValue::QueryInterface(iid, ppv); + if (SUCCEEDED(hr)) return hr; + } + + if (!*ppv && iid == IID_IGeckoCustom) { + RefPtr<GeckoCustom> gkCrap = new GeckoCustom(this); + gkCrap.forget(ppv); + return S_OK; + } + + if (nullptr == *ppv) return E_NOINTERFACE; + + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; +} + +//----------------------------------------------------- +// IAccessible methods +//----------------------------------------------------- + +STDMETHODIMP +AccessibleWrap::get_accParent(IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) { + if (!ppdispParent) return E_INVALIDARG; + + *ppdispParent = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + Accessible* xpParentAcc = Parent(); + if (!xpParentAcc) return S_FALSE; + + *ppdispParent = NativeAccessible(xpParentAcc); + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accChildCount(long __RPC_FAR* pcountChildren) { + if (!pcountChildren) return E_INVALIDARG; + + *pcountChildren = 0; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (nsAccUtils::MustPrune(this)) return S_OK; + + *pcountChildren = ChildCount(); + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accChild( + /* [in] */ VARIANT varChild, + /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispChild) { + if (!ppdispChild) return E_INVALIDARG; + + *ppdispChild = nullptr; + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + // IAccessible::accChild is used to return this accessible or child accessible + // at the given index or to get an accessible by child ID in the case of + // document accessible. + // The getting an accessible by child ID is used by + // AccessibleObjectFromEvent() called by AT when AT handles our MSAA event. + bool isDefunct = false; + RefPtr<IAccessible> child = GetIAccessibleFor(varChild, &isDefunct); + if (!child) { + return E_INVALIDARG; + } + + if (isDefunct) { + return CO_E_OBJNOTCONNECTED; + } + + child.forget(ppdispChild); + return S_OK; +} + +/** + * This function is a helper for implementing IAccessible methods that accept + * a Child ID as a parameter. If the child ID is CHILDID_SELF, the function + * returns S_OK but a null *aOutInterface. Otherwise, *aOutInterface points + * to the resolved IAccessible. + * + * The CHILDID_SELF case is special because in that case we actually execute + * the implementation of the IAccessible method, whereas in the non-self case, + * we delegate the method call to that object for execution. + * + * A sample invocation of this would look like: + * + * RefPtr<IAccessible> accessible; + * HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + * if (FAILED(hr)) { + * return hr; + * } + * + * if (accessible) { + * return accessible->get_accFoo(kVarChildIdSelf, pszName); + * } + * + * // Implementation for CHILDID_SELF case goes here + */ +HRESULT +AccessibleWrap::ResolveChild(const VARIANT& aVarChild, + IAccessible** aOutInterface) { + MOZ_ASSERT(aOutInterface); + *aOutInterface = nullptr; + + if (aVarChild.vt != VT_I4) { + return E_INVALIDARG; + } + + if (IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + if (aVarChild.lVal == CHILDID_SELF) { + return S_OK; + } + + bool isDefunct = false; + RefPtr<IAccessible> accessible = GetIAccessibleFor(aVarChild, &isDefunct); + if (!accessible) { + return E_INVALIDARG; + } + + if (isDefunct) { + return CO_E_OBJNOTCONNECTED; + } + + accessible.forget(aOutInterface); + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accName( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszName) { + if (!pszName || varChild.vt != VT_I4) return E_INVALIDARG; + + *pszName = nullptr; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accName(kVarChildIdSelf, pszName); + } + + nsAutoString name; + Name(name); + + // The name was not provided, e.g. no alt attribute for an image. A screen + // reader may choose to invent its own accessible name, e.g. from an image src + // attribute. Refer to eNoNameOnPurpose return value. + if (name.IsVoid()) return S_FALSE; + + *pszName = ::SysAllocStringLen(name.get(), name.Length()); + if (!*pszName) return E_OUTOFMEMORY; + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accValue( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszValue) { + if (!pszValue) return E_INVALIDARG; + + *pszValue = nullptr; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accValue(kVarChildIdSelf, pszValue); + } + + nsAutoString value; + Value(value); + + // See bug 438784: need to expose URL on doc's value attribute. For this, + // reverting part of fix for bug 425693 to make this MSAA method behave + // IAccessible2-style. + if (value.IsEmpty()) return S_FALSE; + + *pszValue = ::SysAllocStringLen(value.get(), value.Length()); + if (!*pszValue) return E_OUTOFMEMORY; + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accDescription(VARIANT varChild, + BSTR __RPC_FAR* pszDescription) { + if (!pszDescription) return E_INVALIDARG; + + *pszDescription = nullptr; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accDescription(kVarChildIdSelf, pszDescription); + } + + nsAutoString description; + Description(description); + + *pszDescription = + ::SysAllocStringLen(description.get(), description.Length()); + return *pszDescription ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +AccessibleWrap::get_accRole( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ VARIANT __RPC_FAR* pvarRole) { + if (!pvarRole) return E_INVALIDARG; + + VariantInit(pvarRole); + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accRole(kVarChildIdSelf, pvarRole); + } + + a11y::role geckoRole; +#ifdef DEBUG + NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(this), + "Does not support Text when it should"); +#endif + + geckoRole = Role(); + + uint32_t msaaRole = 0; + +#define ROLE(_geckoRole, stringRole, atkRole, macRole, macSubrole, _msaaRole, \ + ia2Role, androidClass, nameRule) \ + case roles::_geckoRole: \ + msaaRole = _msaaRole; \ + break; + + switch (geckoRole) { +#include "RoleMap.h" + default: + MOZ_CRASH("Unknown role."); + } + +#undef ROLE + + // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call + // the MSAA role a ROLE_OUTLINEITEM for consistency and compatibility. We need + // this because ARIA has a role of "row" for both grid and treegrid + if (geckoRole == roles::ROW) { + Accessible* xpParent = Parent(); + if (xpParent && xpParent->Role() == roles::TREE_TABLE) + msaaRole = ROLE_SYSTEM_OUTLINEITEM; + } + + // -- Try enumerated role + if (msaaRole != USE_ROLE_STRING) { + pvarRole->vt = VT_I4; + pvarRole->lVal = msaaRole; // Normal enumerated role + return S_OK; + } + + // -- Try BSTR role + // Could not map to known enumerated MSAA role like ROLE_BUTTON + // Use BSTR role to expose role attribute or tag name + namespace + nsIContent* content = GetContent(); + if (!content) return E_FAIL; + + if (content->IsElement()) { + nsAutoString roleString; + // Try the role attribute. + content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::role, + roleString); + + if (roleString.IsEmpty()) { + // No role attribute (or it is an empty string). + // Use the tag name. + dom::Document* document = content->GetUncomposedDoc(); + if (!document) return E_FAIL; + + dom::NodeInfo* nodeInfo = content->NodeInfo(); + nodeInfo->GetName(roleString); + + // Only append name space if different from that of current document. + if (!nodeInfo->NamespaceEquals(document->GetDefaultNamespaceID())) { + nsAutoString nameSpaceURI; + nodeInfo->GetNamespaceURI(nameSpaceURI); + roleString += u", "_ns + nameSpaceURI; + } + } + + if (!roleString.IsEmpty()) { + pvarRole->vt = VT_BSTR; + pvarRole->bstrVal = ::SysAllocString(roleString.get()); + return S_OK; + } + } + + return E_FAIL; +} + +STDMETHODIMP +AccessibleWrap::get_accState( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ VARIANT __RPC_FAR* pvarState) { + if (!pvarState) return E_INVALIDARG; + + VariantInit(pvarState); + pvarState->vt = VT_I4; + pvarState->lVal = 0; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accState(kVarChildIdSelf, pvarState); + } + + // MSAA only has 31 states and the lowest 31 bits of our state bit mask + // are the same states as MSAA. + // Note: we map the following Gecko states to different MSAA states: + // REQUIRED -> ALERT_LOW + // ALERT -> ALERT_MEDIUM + // INVALID -> ALERT_HIGH + // CHECKABLE -> MARQUEED + + uint64_t state = State(); + + uint32_t msaaState = 0; + nsAccUtils::To32States(state, &msaaState, nullptr); + pvarState->lVal = msaaState; + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accHelp( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszHelp) { + if (!pszHelp) return E_INVALIDARG; + + *pszHelp = nullptr; + return S_FALSE; +} + +STDMETHODIMP +AccessibleWrap::get_accHelpTopic( + /* [out] */ BSTR __RPC_FAR* pszHelpFile, + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ long __RPC_FAR* pidTopic) { + if (!pszHelpFile || !pidTopic) return E_INVALIDARG; + + *pszHelpFile = nullptr; + *pidTopic = 0; + return S_FALSE; +} + +STDMETHODIMP +AccessibleWrap::get_accKeyboardShortcut( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) { + if (!pszKeyboardShortcut) return E_INVALIDARG; + *pszKeyboardShortcut = nullptr; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accKeyboardShortcut(kVarChildIdSelf, + pszKeyboardShortcut); + } + + KeyBinding keyBinding = AccessKey(); + if (keyBinding.IsEmpty()) keyBinding = KeyboardShortcut(); + + nsAutoString shortcut; + keyBinding.ToString(shortcut); + + *pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), shortcut.Length()); + return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +AccessibleWrap::get_accFocus( + /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) { + if (!pvarChild) return E_INVALIDARG; + + VariantInit(pvarChild); + + // clang-format off + // VT_EMPTY: None. This object does not have the keyboard focus itself + // and does not contain a child that has the keyboard focus. + // VT_I4: lVal is CHILDID_SELF. The object itself has the keyboard focus. + // VT_I4: lVal contains the child ID of the child element with the keyboard focus. + // VT_DISPATCH: pdispVal member is the address of the IDispatch interface + // for the child object with the keyboard focus. + // clang-format on + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + // Return the current IAccessible child that has focus + Accessible* focusedAccessible = FocusedChild(); + + if (focusedAccessible == this) { + pvarChild->vt = VT_I4; + pvarChild->lVal = CHILDID_SELF; + } else if (focusedAccessible) { + pvarChild->vt = VT_DISPATCH; + pvarChild->pdispVal = NativeAccessible(focusedAccessible); + } else { + pvarChild->vt = VT_EMPTY; // No focus or focus is not a child + } + + return S_OK; +} + +/** + * This helper class implements IEnumVARIANT for a nsTArray containing + * accessible objects. + */ +class AccessibleEnumerator final : public IEnumVARIANT { + public: + explicit AccessibleEnumerator(const nsTArray<Accessible*>& aArray) + : mArray(aArray.Clone()), mCurIndex(0) {} + AccessibleEnumerator(const AccessibleEnumerator& toCopy) + : mArray(toCopy.mArray.Clone()), mCurIndex(toCopy.mCurIndex) {} + ~AccessibleEnumerator() {} + + // IUnknown + DECL_IUNKNOWN + + // IEnumVARIANT + STDMETHODIMP Next(unsigned long celt, VARIANT FAR* rgvar, + unsigned long FAR* pceltFetched); + STDMETHODIMP Skip(unsigned long celt); + STDMETHODIMP Reset() { + mCurIndex = 0; + return S_OK; + } + STDMETHODIMP Clone(IEnumVARIANT FAR* FAR* ppenum); + + private: + nsTArray<Accessible*> mArray; + uint32_t mCurIndex; +}; + +STDMETHODIMP +AccessibleEnumerator::QueryInterface(REFIID iid, void** ppvObject) { + if (iid == IID_IEnumVARIANT) { + *ppvObject = static_cast<IEnumVARIANT*>(this); + AddRef(); + return S_OK; + } + if (iid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + AddRef(); + return S_OK; + } + + *ppvObject = nullptr; + return E_NOINTERFACE; +} + +STDMETHODIMP +AccessibleEnumerator::Next(unsigned long celt, VARIANT FAR* rgvar, + unsigned long FAR* pceltFetched) { + uint32_t length = mArray.Length(); + HRESULT hr = S_OK; + + // Can't get more elements than there are... + if (celt > length - mCurIndex) { + hr = S_FALSE; + celt = length - mCurIndex; + } + + // Copy the elements of the array into rgvar. + for (uint32_t i = 0; i < celt; ++i, ++mCurIndex) { + rgvar[i].vt = VT_DISPATCH; + rgvar[i].pdispVal = AccessibleWrap::NativeAccessible(mArray[mCurIndex]); + } + + if (pceltFetched) *pceltFetched = celt; + + return hr; +} + +STDMETHODIMP +AccessibleEnumerator::Clone(IEnumVARIANT FAR* FAR* ppenum) { + *ppenum = new AccessibleEnumerator(*this); + if (!*ppenum) return E_OUTOFMEMORY; + NS_ADDREF(*ppenum); + return S_OK; +} + +STDMETHODIMP +AccessibleEnumerator::Skip(unsigned long celt) { + uint32_t length = mArray.Length(); + // Check if we can skip the requested number of elements + if (celt > length - mCurIndex) { + mCurIndex = length; + return S_FALSE; + } + mCurIndex += celt; + return S_OK; +} + +/** + * This method is called when a client wants to know which children of a node + * are selected. Note that this method can only find selected children for + * accessible object which implement SelectAccessible. + * + * The VARIANT return value arguement is expected to either contain a single + * IAccessible or an IEnumVARIANT of IAccessibles. We return the IEnumVARIANT + * regardless of the number of children selected, unless there are none selected + * in which case we return an empty VARIANT. + * + * We get the selected options from the select's accessible object and wrap + * those in an AccessibleEnumerator which we then put in the return VARIANT. + * + * returns a VT_EMPTY VARIANT if: + * - there are no selected children for this object + * - the object is not the type that can have children selected + */ +STDMETHODIMP +AccessibleWrap::get_accSelection(VARIANT __RPC_FAR* pvarChildren) { + if (!pvarChildren) return E_INVALIDARG; + + VariantInit(pvarChildren); + pvarChildren->vt = VT_EMPTY; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!IsSelect()) { + return S_OK; + } + + AutoTArray<Accessible*, 10> selectedItems; + SelectedItems(&selectedItems); + uint32_t count = selectedItems.Length(); + if (count == 1) { + pvarChildren->vt = VT_DISPATCH; + pvarChildren->pdispVal = NativeAccessible(selectedItems[0]); + } else if (count > 1) { + RefPtr<AccessibleEnumerator> pEnum = + new AccessibleEnumerator(selectedItems); + AssociateCOMObjectForDisconnection(pEnum); + pvarChildren->vt = + VT_UNKNOWN; // this must be VT_UNKNOWN for an IEnumVARIANT + NS_ADDREF(pvarChildren->punkVal = pEnum); + } + // If count == 0, vt is already VT_EMPTY, so there's nothing else to do. + + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::get_accDefaultAction( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszDefaultAction) { + if (!pszDefaultAction) return E_INVALIDARG; + + *pszDefaultAction = nullptr; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->get_accDefaultAction(kVarChildIdSelf, pszDefaultAction); + } + + nsAutoString defaultAction; + ActionNameAt(0, defaultAction); + + *pszDefaultAction = + ::SysAllocStringLen(defaultAction.get(), defaultAction.Length()); + return *pszDefaultAction ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +AccessibleWrap::accSelect( + /* [in] */ long flagsSelect, + /* [optional][in] */ VARIANT varChild) { + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->accSelect(flagsSelect, kVarChildIdSelf); + } + + if (flagsSelect & SELFLAG_TAKEFOCUS) { + if (XRE_IsContentProcess()) { + // In this case we might have been invoked while the IPC MessageChannel is + // waiting on a sync reply. We cannot dispatch additional IPC while that + // is happening, so we dispatch TakeFocus from the main thread to + // guarantee that we are outside any IPC. + nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod( + "Accessible::TakeFocus", this, &Accessible::TakeFocus); + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + return S_OK; + } + TakeFocus(); + return S_OK; + } + + if (flagsSelect & SELFLAG_TAKESELECTION) { + TakeSelection(); + return S_OK; + } + + if (flagsSelect & SELFLAG_ADDSELECTION) { + SetSelected(true); + return S_OK; + } + + if (flagsSelect & SELFLAG_REMOVESELECTION) { + SetSelected(false); + return S_OK; + } + + return E_FAIL; +} + +STDMETHODIMP +AccessibleWrap::accLocation( + /* [out] */ long __RPC_FAR* pxLeft, + /* [out] */ long __RPC_FAR* pyTop, + /* [out] */ long __RPC_FAR* pcxWidth, + /* [out] */ long __RPC_FAR* pcyHeight, + /* [optional][in] */ VARIANT varChild) { + if (!pxLeft || !pyTop || !pcxWidth || !pcyHeight) return E_INVALIDARG; + + *pxLeft = 0; + *pyTop = 0; + *pcxWidth = 0; + *pcyHeight = 0; + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, + kVarChildIdSelf); + } + + if (IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + nsIntRect rect = Bounds(); + + *pxLeft = rect.X(); + *pyTop = rect.Y(); + *pcxWidth = rect.Width(); + *pcyHeight = rect.Height(); + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::accNavigate( + /* [in] */ long navDir, + /* [optional][in] */ VARIANT varStart, + /* [retval][out] */ VARIANT __RPC_FAR* pvarEndUpAt) { + if (!pvarEndUpAt) return E_INVALIDARG; + + VariantInit(pvarEndUpAt); + + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varStart, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->accNavigate(navDir, kVarChildIdSelf, pvarEndUpAt); + } + + Accessible* navAccessible = nullptr; + Maybe<RelationType> xpRelation; + +#define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \ + case msaaType: \ + xpRelation.emplace(RelationType::geckoType); \ + break; + + switch (navDir) { + case NAVDIR_FIRSTCHILD: + if (IsProxy()) { + if (!nsAccUtils::MustPrune(Proxy())) { + navAccessible = WrapperFor(Proxy()->FirstChild()); + } + } else { + if (!nsAccUtils::MustPrune(this)) navAccessible = FirstChild(); + } + break; + case NAVDIR_LASTCHILD: + if (IsProxy()) { + if (!nsAccUtils::MustPrune(Proxy())) { + navAccessible = WrapperFor(Proxy()->LastChild()); + } + } else { + if (!nsAccUtils::MustPrune(this)) navAccessible = LastChild(); + } + break; + case NAVDIR_NEXT: + navAccessible = + IsProxy() ? WrapperFor(Proxy()->NextSibling()) : NextSibling(); + break; + case NAVDIR_PREVIOUS: + navAccessible = + IsProxy() ? WrapperFor(Proxy()->PrevSibling()) : PrevSibling(); + break; + case NAVDIR_DOWN: + case NAVDIR_LEFT: + case NAVDIR_RIGHT: + case NAVDIR_UP: + return E_NOTIMPL; + + // MSAA relationship extensions to accNavigate +#include "RelationTypeMap.h" + + default: + return E_INVALIDARG; + } + +#undef RELATIONTYPE + + pvarEndUpAt->vt = VT_EMPTY; + + if (xpRelation) { + Relation rel = RelationByType(*xpRelation); + navAccessible = rel.Next(); + } + + if (!navAccessible) return E_FAIL; + + pvarEndUpAt->pdispVal = NativeAccessible(navAccessible); + pvarEndUpAt->vt = VT_DISPATCH; + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::accHitTest( + /* [in] */ long xLeft, + /* [in] */ long yTop, + /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) { + if (!pvarChild) return E_INVALIDARG; + + VariantInit(pvarChild); + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + Accessible* accessible = ChildAtPoint(xLeft, yTop, eDirectChild); + + // if we got a child + if (accessible) { + // if the child is us + if (accessible == this) { + pvarChild->vt = VT_I4; + pvarChild->lVal = CHILDID_SELF; + } else { // its not create an Accessible for it. + pvarChild->vt = VT_DISPATCH; + pvarChild->pdispVal = NativeAccessible(accessible); + } + } else { + // no child at that point + pvarChild->vt = VT_EMPTY; + return S_FALSE; + } + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::accDoDefaultAction( + /* [optional][in] */ VARIANT varChild) { + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->accDoDefaultAction(kVarChildIdSelf); + } + + return DoAction(0) ? S_OK : E_INVALIDARG; +} + +STDMETHODIMP +AccessibleWrap::put_accName( + /* [optional][in] */ VARIANT varChild, + /* [in] */ BSTR szName) { + return E_NOTIMPL; +} + +STDMETHODIMP +AccessibleWrap::put_accValue( + /* [optional][in] */ VARIANT varChild, + /* [in] */ BSTR szValue) { + RefPtr<IAccessible> accessible; + HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible)); + if (FAILED(hr)) { + return hr; + } + + if (accessible) { + return accessible->put_accValue(kVarChildIdSelf, szValue); + } + + HyperTextAccessible* ht = AsHyperText(); + if (!ht) { + return E_NOTIMPL; + } + + uint32_t length = ::SysStringLen(szValue); + nsAutoString text(szValue, length); + ht->ReplaceText(text); + return S_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// IDispatch + +STDMETHODIMP +AccessibleWrap::GetTypeInfoCount(UINT* pctinfo) { + if (!pctinfo) return E_INVALIDARG; + + *pctinfo = 1; + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { + if (!ppTInfo) return E_INVALIDARG; + + *ppTInfo = nullptr; + + if (iTInfo != 0) return DISP_E_BADINDEX; + + ITypeInfo* typeInfo = GetTI(lcid); + if (!typeInfo) return E_FAIL; + + typeInfo->AddRef(); + *ppTInfo = typeInfo; + + return S_OK; +} + +STDMETHODIMP +AccessibleWrap::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) { + ITypeInfo* typeInfo = GetTI(lcid); + if (!typeInfo) return E_FAIL; + + HRESULT hr = DispGetIDsOfNames(typeInfo, rgszNames, cNames, rgDispId); + return hr; +} + +STDMETHODIMP +AccessibleWrap::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, UINT* puArgErr) { + ITypeInfo* typeInfo = GetTI(lcid); + if (!typeInfo) return E_FAIL; + + return typeInfo->Invoke(static_cast<IAccessible*>(this), dispIdMember, wFlags, + pDispParams, pVarResult, pExcepInfo, puArgErr); +} + +void AccessibleWrap::GetNativeInterface(void** aOutAccessible) { + *aOutAccessible = static_cast<IAccessible*>(this); + NS_ADDREF_THIS(); +} + +void AccessibleWrap::SetID(uint32_t aID) { + MOZ_ASSERT(XRE_IsParentProcess() && IsProxy()); + mID = aID; +} + +static bool IsHandlerInvalidationNeeded(uint32_t aEvent) { + // We want to return true for any events that would indicate that something + // in the handler cache is out of date. + switch (aEvent) { + case EVENT_OBJECT_STATECHANGE: + case EVENT_OBJECT_LOCATIONCHANGE: + case EVENT_OBJECT_NAMECHANGE: + case EVENT_OBJECT_DESCRIPTIONCHANGE: + case EVENT_OBJECT_VALUECHANGE: + case EVENT_OBJECT_FOCUS: + case IA2_EVENT_ACTION_CHANGED: + case IA2_EVENT_DOCUMENT_LOAD_COMPLETE: + case IA2_EVENT_DOCUMENT_LOAD_STOPPED: + case IA2_EVENT_DOCUMENT_ATTRIBUTE_CHANGED: + case IA2_EVENT_DOCUMENT_CONTENT_CHANGED: + case IA2_EVENT_PAGE_CHANGED: + case IA2_EVENT_TEXT_ATTRIBUTE_CHANGED: + case IA2_EVENT_TEXT_CHANGED: + case IA2_EVENT_TEXT_INSERTED: + case IA2_EVENT_TEXT_REMOVED: + case IA2_EVENT_TEXT_UPDATED: + case IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED: + return true; + default: + return false; + } +} + +void AccessibleWrap::FireWinEvent(Accessible* aTarget, uint32_t aEventType) { + MOZ_ASSERT(XRE_IsParentProcess()); + static_assert(sizeof(gWinEventMap) / sizeof(gWinEventMap[0]) == + nsIAccessibleEvent::EVENT_LAST_ENTRY, + "MSAA event map skewed"); + + NS_ASSERTION(aEventType > 0 && aEventType < ArrayLength(gWinEventMap), + "invalid event type"); + + uint32_t winEvent = gWinEventMap[aEventType]; + if (!winEvent) return; + + int32_t childID = GetChildIDFor(aTarget); + if (!childID) return; // Can't fire an event without a child ID + + HWND hwnd = GetHWNDFor(aTarget); + if (!hwnd) { + return; + } + + if (IsHandlerInvalidationNeeded(winEvent)) { + InvalidateHandlers(); + } + + // Fire MSAA event for client area window. + ::NotifyWinEvent(winEvent, hwnd, OBJID_CLIENT, childID); +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessible + +nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { + nsresult rv = Accessible::HandleAccEvent(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + + if (IPCAccessibilityActive()) { + return NS_OK; + } + + uint32_t eventType = aEvent->GetEventType(); + + // Means we're not active. + NS_ENSURE_TRUE(!IsDefunct(), NS_ERROR_FAILURE); + + Accessible* accessible = aEvent->GetAccessible(); + if (!accessible) return NS_OK; + + if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED || + eventType == nsIAccessibleEvent::EVENT_FOCUS) { + UpdateSystemCaretFor(accessible); + } + + FireWinEvent(accessible, eventType); + + return NS_OK; +} + +DocProxyAccessibleWrap* AccessibleWrap::DocProxyWrapper() const { + MOZ_ASSERT(IsProxy()); + + ProxyAccessible* proxy = Proxy(); + if (!proxy) { + return nullptr; + } + + AccessibleWrap* acc = WrapperFor(proxy->Document()); + MOZ_ASSERT(acc->IsDoc()); + + return static_cast<DocProxyAccessibleWrap*>(acc); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccessibleWrap + +//------- Helper methods --------- + +int32_t AccessibleWrap::GetChildIDFor(Accessible* aAccessible) { + // A child ID of the window is required, when we use NotifyWinEvent, + // so that the 3rd party application can call back and get the IAccessible + // the event occurred on. + + if (!aAccessible) { + return 0; + } + + // Chrome should use mID which has been generated by the content process. + if (aAccessible->IsProxy()) { + const uint32_t id = static_cast<AccessibleWrap*>(aAccessible)->mID; + MOZ_ASSERT(id != kNoID); + return id; + } + + if (!aAccessible->Document()) return 0; + + uint32_t* id = &static_cast<AccessibleWrap*>(aAccessible)->mID; + if (*id != kNoID) return *id; + + *id = sIDGen.GetID(); + + MOZ_ASSERT(!aAccessible->IsProxy()); + DocAccessibleWrap* doc = + static_cast<DocAccessibleWrap*>(aAccessible->Document()); + doc->AddID(*id, static_cast<AccessibleWrap*>(aAccessible)); + + return *id; +} + +HWND AccessibleWrap::GetHWNDFor(Accessible* aAccessible) { + if (!aAccessible) { + return nullptr; + } + + if (aAccessible->IsProxy()) { + ProxyAccessible* proxy = aAccessible->Proxy(); + if (!proxy) { + return nullptr; + } + + // If window emulation is enabled, retrieve the emulated window from the + // containing document document proxy. + if (nsWinUtils::IsWindowEmulationStarted()) { + DocAccessibleParent* doc = proxy->Document(); + HWND hWnd = doc->GetEmulatedWindowHandle(); + if (hWnd) { + return hWnd; + } + } + + // Accessibles in child processes are said to have the HWND of the window + // their tab is within. Popups are always in the parent process, and so + // never proxied, which means this is basically correct. + Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser(); + if (!outerDoc) { + // In some cases, the outer document accessible may be unattached from its + // document at this point, if it is scheduled for removal. Do not assert + // in such case. An example: putting aria-hidden="true" on HTML:iframe + // element will destroy iframe's document asynchroniously, but + // the document may be a target of selection events until then, and thus + // it may attempt to deliever these events to MSAA clients. + return nullptr; + } + + return GetHWNDFor(outerDoc); + } + + DocAccessible* document = aAccessible->Document(); + if (!document) return nullptr; + + // Popup lives in own windows, use its HWND until the popup window is + // hidden to make old JAWS versions work with collapsed comboboxes (see + // discussion in bug 379678). + nsIFrame* frame = aAccessible->GetFrame(); + if (frame) { + nsIWidget* widget = frame->GetNearestWidget(); + if (widget && widget->IsVisible()) { + if (nsViewManager* vm = document->PresShellPtr()->GetViewManager()) { + nsCOMPtr<nsIWidget> rootWidget; + vm->GetRootWidget(getter_AddRefs(rootWidget)); + // Make sure the accessible belongs to popup. If not then use + // document HWND (which might be different from root widget in the + // case of window emulation). + if (rootWidget != widget) + return static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); + } + } + } + + return static_cast<HWND>(document->GetNativeWindow()); +} + +IDispatch* AccessibleWrap::NativeAccessible(Accessible* aAccessible) { + if (!aAccessible) { + NS_WARNING("Not passing in an aAccessible"); + return nullptr; + } + + IAccessible* msaaAccessible = nullptr; + aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible)); + return static_cast<IDispatch*>(msaaAccessible); +} + +static Accessible* GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID) { + Accessible* child = + static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID); + if (child) return child; + + uint32_t childDocCount = aDoc->ChildDocumentCount(); + for (uint32_t i = 0; i < childDocCount; i++) { + child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID); + if (child) return child; + } + + return nullptr; +} + +static already_AddRefed<IDispatch> GetProxiedAccessibleInSubtree( + const DocAccessibleParent* aDoc, const VARIANT& aVarChild) { + auto wrapper = static_cast<DocProxyAccessibleWrap*>(WrapperFor(aDoc)); + RefPtr<IAccessible> comProxy; + int32_t docWrapperChildId = AccessibleWrap::GetChildIDFor(wrapper); + // Only document accessible proxies at the top level of their content process + // are created with a pointer to their COM proxy. + if (aDoc->IsTopLevelInContentProcess()) { + wrapper->GetNativeInterface(getter_AddRefs(comProxy)); + } else { + auto tab = static_cast<dom::BrowserParent*>(aDoc->Manager()); + MOZ_ASSERT(tab); + DocAccessibleParent* topLevelDoc = tab->GetTopLevelDocAccessible(); + MOZ_ASSERT(topLevelDoc && topLevelDoc->IsTopLevelInContentProcess()); + VARIANT docId = {{{VT_I4}}}; + docId.lVal = docWrapperChildId; + RefPtr<IDispatch> disp = GetProxiedAccessibleInSubtree(topLevelDoc, docId); + if (!disp) { + return nullptr; + } + + DebugOnly<HRESULT> hr = + disp->QueryInterface(IID_IAccessible, getter_AddRefs(comProxy)); + MOZ_ASSERT(SUCCEEDED(hr)); + } + + MOZ_ASSERT(comProxy); + if (!comProxy) { + return nullptr; + } + + if (docWrapperChildId == aVarChild.lVal) { + return comProxy.forget(); + } + + RefPtr<IDispatch> disp; + if (FAILED(comProxy->get_accChild(aVarChild, getter_AddRefs(disp)))) { + return nullptr; + } + + return disp.forget(); +} + +bool AccessibleWrap::IsRootForHWND() { + if (IsRoot()) { + return true; + } + HWND thisHwnd = GetHWNDFor(this); + AccessibleWrap* parent = static_cast<AccessibleWrap*>(Parent()); + MOZ_ASSERT(parent); + HWND parentHwnd = GetHWNDFor(parent); + return thisHwnd != parentHwnd; +} + +already_AddRefed<IAccessible> AccessibleWrap::GetIAccessibleFor( + const VARIANT& aVarChild, bool* aIsDefunct) { + if (aVarChild.vt != VT_I4) return nullptr; + + VARIANT varChild = aVarChild; + + MOZ_ASSERT(aIsDefunct); + *aIsDefunct = false; + + RefPtr<IAccessible> result; + + if (varChild.lVal == CHILDID_SELF) { + *aIsDefunct = IsDefunct(); + if (*aIsDefunct) { + return nullptr; + } + GetNativeInterface(getter_AddRefs(result)); + if (result) { + return result.forget(); + } + // If we're not a proxy, there's nothing more we can do to attempt to + // resolve the IAccessible, so we just fail. + if (!IsProxy()) { + return nullptr; + } + // Otherwise, since we're a proxy and we have a null native interface, this + // indicates that we need to obtain a COM proxy. To do this, we'll replace + // CHILDID_SELF with our real MSAA ID and continue the search from there. + varChild.lVal = GetExistingID(); + } + + if (varChild.ulVal != GetExistingID() && + (IsProxy() ? nsAccUtils::MustPrune(Proxy()) + : nsAccUtils::MustPrune(this))) { + // This accessible should have no subtree in platform, return null for its + // children. + return nullptr; + } + + // If the MSAA ID is not a chrome id then we already know that we won't + // find it here and should look remotely instead. This handles the case when + // accessible is part of the chrome process and is part of the xul browser + // window and the child id points in the content documents. Thus we need to + // make sure that it is never called on proxies. + // Bug 1422674: We must only handle remote ids here (< 0), not child indices. + // Child indices (> 0) are handled below for both local and remote children. + if (XRE_IsParentProcess() && !IsProxy() && varChild.lVal < 0 && + !sIDGen.IsChromeID(varChild.lVal)) { + if (!IsRootForHWND()) { + // Bug 1422201, 1424657: accChild with a remote id is only valid on the + // root accessible for an HWND. + // Otherwise, we might return remote accessibles which aren't descendants + // of this accessible. This would confuse clients which use accChild to + // check whether something is a descendant of a document. + return nullptr; + } + return GetRemoteIAccessibleFor(varChild); + } + + if (varChild.lVal > 0) { + // Gecko child indices are 0-based in contrast to indices used in MSAA. + MOZ_ASSERT(!IsProxy()); + Accessible* xpAcc = GetChildAt(varChild.lVal - 1); + if (!xpAcc) { + return nullptr; + } + *aIsDefunct = xpAcc->IsDefunct(); + static_cast<AccessibleWrap*>(xpAcc)->GetNativeInterface( + getter_AddRefs(result)); + return result.forget(); + } + + // If lVal negative then it is treated as child ID and we should look for + // accessible through whole accessible subtree including subdocuments. + // Otherwise we treat lVal as index in parent. + // First handle the case that both this accessible and the id'd one are in + // this process. + if (!IsProxy()) { + DocAccessible* document = Document(); + Accessible* child = + GetAccessibleInSubtree(document, static_cast<uint32_t>(varChild.lVal)); + + // If it is a document then just return an accessible. + if (child && IsDoc()) { + *aIsDefunct = child->IsDefunct(); + static_cast<AccessibleWrap*>(child)->GetNativeInterface( + getter_AddRefs(result)); + return result.forget(); + } + + // Otherwise check whether the accessible is a child (this path works for + // ARIA documents and popups). + Accessible* parent = child; + while (parent && parent != document) { + if (parent == this) { + *aIsDefunct = child->IsDefunct(); + static_cast<AccessibleWrap*>(child)->GetNativeInterface( + getter_AddRefs(result)); + return result.forget(); + } + + parent = parent->Parent(); + } + } + + // Now see about the case that both this accessible and the target one are + // proxied. + if (IsProxy()) { + DocAccessibleParent* proxyDoc = Proxy()->Document(); + RefPtr<IDispatch> disp = GetProxiedAccessibleInSubtree(proxyDoc, varChild); + if (!disp) { + return nullptr; + } + + MOZ_ASSERT(mscom::IsProxy(disp)); + DebugOnly<HRESULT> hr = + disp->QueryInterface(IID_IAccessible, getter_AddRefs(result)); + MOZ_ASSERT(SUCCEEDED(hr)); + return result.forget(); + } + + return nullptr; +} + +/** + * Visit DocAccessibleParent descendants of `aBrowser` that are at the top + * level of their content process. + * That is, IsTopLevelInContentProcess() will be true for each visited actor. + * Each visited actor will be an embedded document in a different content + * process to its embedder. + * The DocAccessibleParent for `aBrowser` itself is excluded. + * `aCallback` will be called for each DocAccessibleParent. + * The callback should return true to continue traversal, false to cease. + */ +template <typename Callback> +static bool VisitDocAccessibleParentDescendantsAtTopLevelInContentProcess( + dom::BrowserParent* aBrowser, Callback aCallback) { + // We can't use BrowserBridgeParent::VisitAllDescendants because it doesn't + // provide a way to stop the search. + const auto& bridges = aBrowser->ManagedPBrowserBridgeParent(); + for (auto iter = bridges.ConstIter(); !iter.Done(); iter.Next()) { + auto bridge = static_cast<dom::BrowserBridgeParent*>(iter.Get()->GetKey()); + dom::BrowserParent* childBrowser = bridge->GetBrowserParent(); + DocAccessibleParent* childDocAcc = childBrowser->GetTopLevelDocAccessible(); + if (!childDocAcc || childDocAcc->IsShutdown()) { + continue; + } + if (!aCallback(childDocAcc)) { + return false; // Stop traversal. + } + if (!VisitDocAccessibleParentDescendantsAtTopLevelInContentProcess( + childBrowser, aCallback)) { + return false; // Stop traversal. + } + } + return true; // Continue traversal. +} + +already_AddRefed<IAccessible> AccessibleWrap::GetRemoteIAccessibleFor( + const VARIANT& aVarChild) { + a11y::RootAccessible* root = RootAccessible(); + const nsTArray<DocAccessibleParent*>* remoteDocs = + DocManager::TopLevelRemoteDocs(); + if (!remoteDocs) { + return nullptr; + } + + RefPtr<IAccessible> result; + + // We intentionally leave the call to remoteDocs->Length() inside the loop + // condition because it is possible for reentry to occur in the call to + // GetProxiedAccessibleInSubtree() such that remoteDocs->Length() is mutated. + for (size_t i = 0; i < remoteDocs->Length(); i++) { + DocAccessibleParent* topRemoteDoc = remoteDocs->ElementAt(i); + + Accessible* outerDoc = topRemoteDoc->OuterDocOfRemoteBrowser(); + if (!outerDoc) { + continue; + } + + if (outerDoc->RootAccessible() != root) { + continue; + } + + RefPtr<IDispatch> disp; + auto checkDoc = [&aVarChild, + &disp](DocAccessibleParent* aRemoteDoc) -> bool { + uint32_t remoteDocMsaaId = WrapperFor(aRemoteDoc)->GetExistingID(); + if (!sIDGen.IsSameContentProcessFor(aVarChild.lVal, remoteDocMsaaId)) { + return true; // Continue the search. + } + if ((disp = GetProxiedAccessibleInSubtree(aRemoteDoc, aVarChild))) { + return false; // Found it! Stop traversal! + } + return true; // Continue the search. + }; + + // Check the top level document for this id. + checkDoc(topRemoteDoc); + if (!disp) { + // The top level document doesn't contain this id. Recursively check any + // out-of-process iframe documents it embeds. + VisitDocAccessibleParentDescendantsAtTopLevelInContentProcess( + static_cast<dom::BrowserParent*>(topRemoteDoc->Manager()), checkDoc); + } + + if (!disp) { + continue; + } + + DebugOnly<HRESULT> hr = + disp->QueryInterface(IID_IAccessible, getter_AddRefs(result)); + // QI can fail on rare occasions if the Accessible dies after we fetched + // disp but before we QI. + NS_WARNING_ASSERTION(SUCCEEDED(hr), "QI failed on remote IDispatch"); + return result.forget(); + } + + return nullptr; +} + +void AccessibleWrap::UpdateSystemCaretFor(Accessible* aAccessible) { + // Move the system caret so that Windows Tablet Edition and tradional ATs with + // off-screen model can follow the caret + ::DestroyCaret(); + + HyperTextAccessible* text = aAccessible->AsHyperText(); + if (!text) return; + + nsIWidget* widget = nullptr; + LayoutDeviceIntRect caretRect = text->GetCaretRect(&widget); + + if (!widget) { + return; + } + + HWND caretWnd = + reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); + UpdateSystemCaretFor(caretWnd, caretRect); +} + +/* static */ +void AccessibleWrap::UpdateSystemCaretFor( + ProxyAccessible* aProxy, const LayoutDeviceIntRect& aCaretRect) { + ::DestroyCaret(); + + // The HWND should be the real widget HWND, not an emulated HWND. + // We get the HWND from the proxy's outer doc to bypass window emulation. + Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser(); + UpdateSystemCaretFor(GetHWNDFor(outerDoc), aCaretRect); +} + +/* static */ +void AccessibleWrap::UpdateSystemCaretFor( + HWND aCaretWnd, const LayoutDeviceIntRect& aCaretRect) { + if (!aCaretWnd || aCaretRect.IsEmpty()) { + return; + } + + // Create invisible bitmap for caret, otherwise its appearance interferes + // with Gecko caret + nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.Height(), 1, 1, nullptr)); + if (::CreateCaret(aCaretWnd, caretBitMap, 1, + aCaretRect.Height())) { // Also destroys the last caret + ::ShowCaret(aCaretWnd); + RECT windowRect; + ::GetWindowRect(aCaretWnd, &windowRect); + ::SetCaretPos(aCaretRect.X() - windowRect.left, + aCaretRect.Y() - windowRect.top); + } +} + +ITypeInfo* AccessibleWrap::GetTI(LCID lcid) { + if (gTypeInfo) return gTypeInfo; + + ITypeLib* typeLib = nullptr; + HRESULT hr = LoadRegTypeLib(LIBID_Accessibility, 1, 0, lcid, &typeLib); + if (FAILED(hr)) return nullptr; + + hr = typeLib->GetTypeInfoOfGuid(IID_IAccessible, &gTypeInfo); + typeLib->Release(); + + if (FAILED(hr)) return nullptr; + + return gTypeInfo; +} + +/* static */ +uint32_t AccessibleWrap::GetContentProcessIdFor( + dom::ContentParentId aIPCContentId) { + return sIDGen.GetContentProcessIDFor(aIPCContentId); +} + +/* static */ +void AccessibleWrap::ReleaseContentProcessIdFor( + dom::ContentParentId aIPCContentId) { + sIDGen.ReleaseContentProcessIDFor(aIPCContentId); +} + +/* static */ +void AccessibleWrap::SetHandlerControl(DWORD aPid, + RefPtr<IHandlerControl> aCtrl) { + MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread()); + + if (!sHandlerControllers) { + sHandlerControllers = new nsTArray<HandlerControllerData>(); + ClearOnShutdown(&sHandlerControllers); + } + + HandlerControllerData ctrlData(aPid, std::move(aCtrl)); + if (sHandlerControllers->Contains(ctrlData)) { + return; + } + + sHandlerControllers->AppendElement(std::move(ctrlData)); +} + +/* static */ +void AccessibleWrap::InvalidateHandlers() { + static const HRESULT kErrorServerDied = + HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE); + + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!sHandlerControllers || sHandlerControllers->IsEmpty()) { + return; + } + + // We iterate in reverse so that we may safely remove defunct elements while + // executing the loop. + for (auto& controller : Reversed(*sHandlerControllers)) { + MOZ_ASSERT(controller.mPid); + MOZ_ASSERT(controller.mCtrl); + + ASYNC_INVOKER_FOR(IHandlerControl) + invoker(controller.mCtrl, Some(controller.mIsProxy)); + + HRESULT hr = ASYNC_INVOKE(invoker, Invalidate); + + if (hr == CO_E_OBJNOTCONNECTED || hr == kErrorServerDied) { + sHandlerControllers->RemoveElement(controller); + } else { + Unused << NS_WARN_IF(FAILED(hr)); + } + } +} + +bool AccessibleWrap::DispatchTextChangeToHandler(bool aIsInsert, + const nsString& aText, + int32_t aStart, + uint32_t aLen) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!sHandlerControllers || sHandlerControllers->IsEmpty()) { + return false; + } + + HWND hwnd = GetHWNDFor(this); + MOZ_ASSERT(hwnd); + if (!hwnd) { + return false; + } + + long msaaId = GetChildIDFor(this); + + DWORD ourPid = ::GetCurrentProcessId(); + + // The handler ends up calling NotifyWinEvent, which should only be done once + // since it broadcasts the same event to every process who is subscribed. + // OTOH, if our chrome process contains a handler, we should prefer to + // broadcast the event from that process, as we want any DLLs injected by ATs + // to receive the event synchronously. Otherwise we simply choose the first + // handler in the list, for the lack of a better heuristic. + + nsTArray<HandlerControllerData>::index_type ctrlIndex = + sHandlerControllers->IndexOf(ourPid); + + if (ctrlIndex == nsTArray<HandlerControllerData>::NoIndex) { + ctrlIndex = 0; + } + + HandlerControllerData& controller = sHandlerControllers->ElementAt(ctrlIndex); + MOZ_ASSERT(controller.mPid); + MOZ_ASSERT(controller.mCtrl); + + VARIANT_BOOL isInsert = aIsInsert ? VARIANT_TRUE : VARIANT_FALSE; + + IA2TextSegment textSegment{::SysAllocStringLen(aText.get(), aText.Length()), + aStart, aStart + static_cast<long>(aLen)}; + + ASYNC_INVOKER_FOR(IHandlerControl) + invoker(controller.mCtrl, Some(controller.mIsProxy)); + + HRESULT hr = ASYNC_INVOKE(invoker, OnTextChange, PtrToLong(hwnd), msaaId, + isInsert, &textSegment); + + ::SysFreeString(textSegment.text); + + return SUCCEEDED(hr); +} + +/* static */ +void AccessibleWrap::AssignChildIDTo(NotNull<sdnAccessible*> aSdnAcc) { + aSdnAcc->SetUniqueID(sIDGen.GetID()); +} + +/* static */ +void AccessibleWrap::ReleaseChildID(NotNull<sdnAccessible*> aSdnAcc) { + sIDGen.ReleaseID(aSdnAcc); +} diff --git a/accessible/windows/msaa/AccessibleWrap.h b/accessible/windows/msaa/AccessibleWrap.h new file mode 100644 index 0000000000..7863f8199c --- /dev/null +++ b/accessible/windows/msaa/AccessibleWrap.h @@ -0,0 +1,329 @@ +/* -*- 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_AccessibleWrap_h_ +#define mozilla_a11y_AccessibleWrap_h_ + +#include "nsCOMPtr.h" +#include "Accessible.h" +#include "ia2Accessible.h" +#include "ia2AccessibleComponent.h" +#include "ia2AccessibleHyperlink.h" +#include "ia2AccessibleValue.h" +#include "mozilla/a11y/AccessibleHandler.h" +#include "mozilla/a11y/MsaaIdGenerator.h" +#include "mozilla/a11y/ProxyAccessible.h" +#include "mozilla/Attributes.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/StaticPtr.h" +#include "nsXULAppAPI.h" +#include "Units.h" + +#if defined(__GNUC__) || defined(__clang__) +// Inheriting from both XPCOM and MSCOM interfaces causes a lot of warnings +// about virtual functions being hidden by each other. This is done by +// design, so silence the warning. +# pragma GCC diagnostic ignored "-Woverloaded-virtual" +#endif + +namespace mozilla { +namespace a11y { +class DocProxyAccessibleWrap; + +class AccessibleWrap : public Accessible, + public ia2Accessible, + public ia2AccessibleComponent, + public ia2AccessibleHyperlink, + public ia2AccessibleValue { + public: // construction, destruction + AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + public: // IUnknown methods - see iunknown.h for documentation + STDMETHODIMP QueryInterface(REFIID, void**) override; + + // Return the registered OLE class ID of this object's CfDataObj. + CLSID GetClassID() const; + + public: // COM interface IAccessible + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accParent( + /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) + override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChildCount( + /* [retval][out] */ long __RPC_FAR* pcountChildren) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild( + /* [in] */ VARIANT varChild, + /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispChild) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accName( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszName) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszValue) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accDescription( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszDescription) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accRole( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ VARIANT __RPC_FAR* pvarRole) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accState( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ VARIANT __RPC_FAR* pvarState) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accHelp( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszHelp) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accHelpTopic( + /* [out] */ BSTR __RPC_FAR* pszHelpFile, + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ long __RPC_FAR* pidTopic) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accFocus( + /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accSelection( + /* [retval][out] */ VARIANT __RPC_FAR* pvarChildren) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accDefaultAction( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszDefaultAction) override; + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE accSelect( + /* [in] */ long flagsSelect, + /* [optional][in] */ VARIANT varChild) override; + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE accLocation( + /* [out] */ long __RPC_FAR* pxLeft, + /* [out] */ long __RPC_FAR* pyTop, + /* [out] */ long __RPC_FAR* pcxWidth, + /* [out] */ long __RPC_FAR* pcyHeight, + /* [optional][in] */ VARIANT varChild) override; + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE accNavigate( + /* [in] */ long navDir, + /* [optional][in] */ VARIANT varStart, + /* [retval][out] */ VARIANT __RPC_FAR* pvarEndUpAt) override; + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE accHitTest( + /* [in] */ long xLeft, + /* [in] */ long yTop, + /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) override; + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE accDoDefaultAction( + /* [optional][in] */ VARIANT varChild) override; + + virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_accName( + /* [optional][in] */ VARIANT varChild, + /* [in] */ BSTR szName) override; + + virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_accValue( + /* [optional][in] */ VARIANT varChild, + /* [in] */ BSTR szValue) override; + + // IDispatch (support of scripting languages like VB) + virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo) override; + + virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo** ppTInfo) override; + + virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, + LPOLESTR* rgszNames, + UINT cNames, LCID lcid, + DISPID* rgDispId) override; + + virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, + LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, + VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, + UINT* puArgErr) override; + + // Accessible + virtual nsresult HandleAccEvent(AccEvent* aEvent) override; + virtual void Shutdown() override; + + // Helper methods + static int32_t GetChildIDFor(Accessible* aAccessible); + static HWND GetHWNDFor(Accessible* aAccessible); + + static void FireWinEvent(Accessible* aTarget, uint32_t aEventType); + + /** + * System caret support: update the Windows caret position. + * The system caret works more universally than the MSAA caret + * For example, Window-Eyes, JAWS, ZoomText and Windows Tablet Edition use it + * We will use an invisible system caret. + * Gecko is still responsible for drawing its own caret + */ + void UpdateSystemCaretFor(Accessible* aAccessible); + static void UpdateSystemCaretFor(ProxyAccessible* aProxy, + const LayoutDeviceIntRect& aCaretRect); + + /** + * Associate a COM object with this Accessible so it will be disconnected + * from remote clients when this Accessible shuts down. + * This should only be called with separate COM objects with a different + * IUnknown to this AccessibleWrap; e.g. IAccessibleRelation. + */ + void AssociateCOMObjectForDisconnection(IUnknown* aObject) { + // We only need to track these for content processes because COM garbage + // collection is disabled there. + if (XRE_IsContentProcess()) { + mAssociatedCOMObjectsForDisconnection.AppendElement(aObject); + } + } + + private: + static void UpdateSystemCaretFor(HWND aCaretWnd, + const LayoutDeviceIntRect& aCaretRect); + + public: + /** + * Determine whether this is the root accessible for its HWND. + */ + bool IsRootForHWND(); + + /** + * Find an accessible by the given child ID in cached documents. + */ + [[nodiscard]] already_AddRefed<IAccessible> GetIAccessibleFor( + const VARIANT& aVarChild, bool* aIsDefunct); + + virtual void GetNativeInterface(void** aOutAccessible) override; + + static IDispatch* NativeAccessible(Accessible* aAccessible); + + uint32_t GetExistingID() const { return mID; } + static const uint32_t kNoID = 0; + void SetID(uint32_t aID); + + static uint32_t GetContentProcessIdFor(dom::ContentParentId aIPCContentId); + static void ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId); + + static void SetHandlerControl(DWORD aPid, RefPtr<IHandlerControl> aCtrl); + + static void InvalidateHandlers(); + + bool DispatchTextChangeToHandler(bool aIsInsert, const nsString& aText, + int32_t aStart, uint32_t aLen); + + static void AssignChildIDTo(NotNull<sdnAccessible*> aSdnAcc); + static void ReleaseChildID(NotNull<sdnAccessible*> aSdnAcc); + + protected: + virtual ~AccessibleWrap(); + + uint32_t mID; + + HRESULT + ResolveChild(const VARIANT& aVarChild, IAccessible** aOutInterface); + + /** + * Find a remote accessible by the given child ID. + */ + [[nodiscard]] already_AddRefed<IAccessible> GetRemoteIAccessibleFor( + const VARIANT& aVarChild); + + /** + * Return the wrapper for the document's proxy. + */ + DocProxyAccessibleWrap* DocProxyWrapper() const; + + /** + * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it. + */ + static ITypeInfo* GetTI(LCID lcid); + + static ITypeInfo* gTypeInfo; + + static MsaaIdGenerator sIDGen; + + enum navRelations { + NAVRELATION_CONTROLLED_BY = 0x1000, + NAVRELATION_CONTROLLER_FOR = 0x1001, + NAVRELATION_LABEL_FOR = 0x1002, + NAVRELATION_LABELLED_BY = 0x1003, + NAVRELATION_MEMBER_OF = 0x1004, + NAVRELATION_NODE_CHILD_OF = 0x1005, + NAVRELATION_FLOWS_TO = 0x1006, + NAVRELATION_FLOWS_FROM = 0x1007, + NAVRELATION_SUBWINDOW_OF = 0x1008, + NAVRELATION_EMBEDS = 0x1009, + NAVRELATION_EMBEDDED_BY = 0x100a, + NAVRELATION_POPUP_FOR = 0x100b, + NAVRELATION_PARENT_WINDOW_OF = 0x100c, + NAVRELATION_DEFAULT_BUTTON = 0x100d, + NAVRELATION_DESCRIBED_BY = 0x100e, + NAVRELATION_DESCRIPTION_FOR = 0x100f, + NAVRELATION_NODE_PARENT_OF = 0x1010, + NAVRELATION_CONTAINING_DOCUMENT = 0x1011, + NAVRELATION_CONTAINING_TAB_PANE = 0x1012, + NAVRELATION_CONTAINING_WINDOW = 0x1013, + NAVRELATION_CONTAINING_APPLICATION = 0x1014, + NAVRELATION_DETAILS = 0x1015, + NAVRELATION_DETAILS_FOR = 0x1016, + NAVRELATION_ERROR = 0x1017, + NAVRELATION_ERROR_FOR = 0x1018 + }; + + struct HandlerControllerData final { + HandlerControllerData(DWORD aPid, RefPtr<IHandlerControl>&& aCtrl) + : mPid(aPid), mCtrl(std::move(aCtrl)) { + mIsProxy = mozilla::mscom::IsProxy(mCtrl); + } + + HandlerControllerData(HandlerControllerData&& aOther) + : mPid(aOther.mPid), + mIsProxy(aOther.mIsProxy), + mCtrl(std::move(aOther.mCtrl)) {} + + bool operator==(const HandlerControllerData& aOther) const { + return mPid == aOther.mPid; + } + + bool operator==(const DWORD& aPid) const { return mPid == aPid; } + + DWORD mPid; + bool mIsProxy; + RefPtr<IHandlerControl> mCtrl; + }; + + static StaticAutoPtr<nsTArray<HandlerControllerData>> sHandlerControllers; + + nsTArray<RefPtr<IUnknown>> mAssociatedCOMObjectsForDisconnection; +}; + +static inline AccessibleWrap* WrapperFor(const ProxyAccessible* aProxy) { + return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper()); +} + +} // namespace a11y +} // namespace mozilla + +#ifdef XP_WIN +// Undo the windows.h damage +# undef GetMessage +# undef CreateEvent +# undef GetClassName +# undef GetBinaryType +# undef RemoveDirectory +#endif + +#endif diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.cpp b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp new file mode 100644 index 0000000000..ef84e471bb --- /dev/null +++ b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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 "ApplicationAccessibleWrap.h" + +#include "AccessibleApplication_i.c" +#include "IUnknownImpl.h" + +#include "nsIGfxInfo.h" +#include "nsPersistentProperties.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Services.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// nsISupports +NS_IMPL_ISUPPORTS_INHERITED0(ApplicationAccessibleWrap, ApplicationAccessible) + +already_AddRefed<nsIPersistentProperties> +ApplicationAccessibleWrap::NativeAttributes() { + RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties(); + + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + if (gfxInfo) { + bool isD2DEnabled = false; + gfxInfo->GetD2DEnabled(&isD2DEnabled); + nsAutoString unused; + attributes->SetStringProperty( + "D2D"_ns, isD2DEnabled ? u"true"_ns : u"false"_ns, unused); + } + + return attributes.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// IUnknown + +STDMETHODIMP +ApplicationAccessibleWrap::QueryInterface(REFIID iid, void** ppv) { + if (!ppv) return E_INVALIDARG; + + *ppv = nullptr; + + if (IID_IAccessibleApplication == iid) { + *ppv = static_cast<IAccessibleApplication*>(this); + (reinterpret_cast<IUnknown*>(*ppv))->AddRef(); + return S_OK; + } + + return AccessibleWrap::QueryInterface(iid, ppv); +} + +//////////////////////////////////////////////////////////////////////////////// +// IAccessibleApplication + +STDMETHODIMP +ApplicationAccessibleWrap::get_appName(BSTR* aName) { + if (!aName) return E_INVALIDARG; + + *aName = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString name; + AppName(name); + if (name.IsEmpty()) return S_FALSE; + + *aName = ::SysAllocStringLen(name.get(), name.Length()); + return *aName ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ApplicationAccessibleWrap::get_appVersion(BSTR* aVersion) { + if (!aVersion) return E_INVALIDARG; + + *aVersion = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString version; + AppVersion(version); + if (version.IsEmpty()) return S_FALSE; + + *aVersion = ::SysAllocStringLen(version.get(), version.Length()); + return *aVersion ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ApplicationAccessibleWrap::get_toolkitName(BSTR* aName) { + if (!aName) return E_INVALIDARG; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString name; + PlatformName(name); + if (name.IsEmpty()) return S_FALSE; + + *aName = ::SysAllocStringLen(name.get(), name.Length()); + return *aName ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +ApplicationAccessibleWrap::get_toolkitVersion(BSTR* aVersion) { + if (!aVersion) return E_INVALIDARG; + + *aVersion = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString version; + PlatformVersion(version); + if (version.IsEmpty()) return S_FALSE; + + *aVersion = ::SysAllocStringLen(version.get(), version.Length()); + return *aVersion ? S_OK : E_OUTOFMEMORY; +} diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.h b/accessible/windows/msaa/ApplicationAccessibleWrap.h new file mode 100644 index 0000000000..48bbaef013 --- /dev/null +++ b/accessible/windows/msaa/ApplicationAccessibleWrap.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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_ApplicationAccessibleWrap_h__ +#define mozilla_a11y_ApplicationAccessibleWrap_h__ + +#include "ApplicationAccessible.h" + +#include "AccessibleApplication.h" + +namespace mozilla { +namespace a11y { + +class ApplicationAccessibleWrap : public ApplicationAccessible, + public IAccessibleApplication { + ~ApplicationAccessibleWrap() {} + + public: + // nsISupporst + NS_DECL_ISUPPORTS_INHERITED + + // nsAccessible + virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID, void**); + + // IAccessibleApplication + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_appName( + /* [retval][out] */ BSTR* name); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_appVersion( + /* [retval][out] */ BSTR* version); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_toolkitName( + /* [retval][out] */ BSTR* name); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_toolkitVersion( + /* [retval][out] */ BSTR* version); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/Compatibility.cpp b/accessible/windows/msaa/Compatibility.cpp new file mode 100644 index 0000000000..43eb407f75 --- /dev/null +++ b/accessible/windows/msaa/Compatibility.cpp @@ -0,0 +1,413 @@ +/* -*- 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 "Compatibility.h" + +#include "mozilla/WindowsVersion.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsExceptionHandler.h" +#include "nsIXULRuntime.h" +#include "nsPrintfCString.h" +#include "nsUnicharUtils.h" +#include "nsWindowsDllInterceptor.h" +#include "nsWinUtils.h" +#include "Statistics.h" + +#include "mozilla/Preferences.h" + +#include <shlobj.h> + +using namespace mozilla; +using namespace mozilla::a11y; + +/** + * String versions of consumer flags. See GetHumanReadableConsumersStr. + */ +static const wchar_t* ConsumerStringMap[CONSUMERS_ENUM_LEN + 1] = { + L"NVDA", L"JAWS", L"OLDJAWS", L"WE", L"DOLPHIN", + L"SEROTEK", L"COBRA", L"ZOOMTEXT", L"KAZAGURU", L"YOUDAO", + L"UNKNOWN", L"UIAUTOMATION", L"VISPEROSHARED", L"\0"}; + +bool Compatibility::IsModuleVersionLessThan(HMODULE aModuleHandle, + unsigned long long aVersion) { + LauncherResult<ModuleVersion> version = GetModuleVersion(aModuleHandle); + if (version.isErr()) { + return true; + } + + return version.unwrap() < aVersion; +} + +//////////////////////////////////////////////////////////////////////////////// +// Compatibility +//////////////////////////////////////////////////////////////////////////////// + +static WindowsDllInterceptor sUser32Interceptor; +static WindowsDllInterceptor::FuncHookType<decltype(&InSendMessageEx)> + sInSendMessageExStub; +static bool sInSendMessageExHackEnabled = false; +static PVOID sVectoredExceptionHandler = nullptr; + +#if defined(_MSC_VER) +# include <intrin.h> +# pragma intrinsic(_ReturnAddress) +# define RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) || defined(__clang__) +# define RETURN_ADDRESS() \ + __builtin_extract_return_addr(__builtin_return_address(0)) +#endif + +static inline bool IsCurrentThreadInBlockingMessageSend( + const DWORD aStateBits) { + // From the MSDN docs for InSendMessageEx + return (aStateBits & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND; +} + +/** + * COM assumes that if you're invoking a proxy from an STA thread while + * InSendMessageEx reports that the calling thread is blocked, that you'll + * deadlock your own process. It returns the RPC_E_CANTCALLOUT_ININPUTSYNCCALL + * error code. This is not actually true in our case: we are calling into + * the multithreaded apartment via ALPC. In this hook, we check to see if the + * caller is COM, and if so, we lie to it. + * + * This hack is necessary for ATs who invoke COM proxies from within + * WH_CALLWNDPROC hooks, WinEvent hooks, or a WndProc handling a sent + * (as opposed to posted) message. + */ +static DWORD WINAPI InSendMessageExHook(LPVOID lpReserved) { + MOZ_ASSERT(XRE_IsParentProcess()); + DWORD result = sInSendMessageExStub(lpReserved); + if (NS_IsMainThread() && sInSendMessageExHackEnabled && + IsCurrentThreadInBlockingMessageSend(result)) { + // We want to take a strong reference to the dll so that it is never + // unloaded/reloaded from this point forward, hence we use LoadLibrary + // and not GetModuleHandle. + static const HMODULE comModule = []() -> HMODULE { + HMODULE module = LoadLibraryW(L"combase.dll"); + if (!module) { + // combase is not present on Windows 7, so we fall back to ole32 there + module = LoadLibraryW(L"ole32.dll"); + } + + return module; + }(); + + MOZ_ASSERT(comModule); + if (!comModule) { + return result; + } + + // Check if InSendMessageEx is being called from code within comModule + HMODULE callingModule; + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCWSTR>(RETURN_ADDRESS()), + &callingModule) && + callingModule == comModule) { + result = ISMEX_NOTIFY; + } + } + return result; +} + +static LONG CALLBACK +DetectInSendMessageExCompat(PEXCEPTION_POINTERS aExceptionInfo) { + DWORD exceptionCode = aExceptionInfo->ExceptionRecord->ExceptionCode; + if (exceptionCode == RPC_E_CANTCALLOUT_ININPUTSYNCCALL && NS_IsMainThread()) { + sInSendMessageExHackEnabled = true; + // We don't need this exception handler anymore, so remove it + if (RemoveVectoredExceptionHandler(sVectoredExceptionHandler)) { + sVectoredExceptionHandler = nullptr; + } + } + return EXCEPTION_CONTINUE_SEARCH; +} + +uint32_t Compatibility::sConsumers = Compatibility::UNKNOWN; + +/** + * This function is safe to call multiple times. + */ +/* static */ +void Compatibility::InitConsumers() { + HMODULE jawsHandle = ::GetModuleHandleW(L"jhook"); + if (jawsHandle) { + sConsumers |= + IsModuleVersionLessThan(jawsHandle, MAKE_FILE_VERSION(19, 0, 0, 0)) + ? OLDJAWS + : JAWS; + } + + if (::GetModuleHandleW(L"gwm32inc")) sConsumers |= WE; + + if (::GetModuleHandleW(L"dolwinhk")) sConsumers |= DOLPHIN; + + if (::GetModuleHandleW(L"STSA32")) sConsumers |= SEROTEK; + + if (::GetModuleHandleW(L"nvdaHelperRemote")) sConsumers |= NVDA; + + if (::GetModuleHandleW(L"OsmHooks") || ::GetModuleHandleW(L"OsmHks64")) + sConsumers |= COBRA; + + if (::GetModuleHandleW(L"WebFinderRemote")) sConsumers |= ZOOMTEXT; + + if (::GetModuleHandleW(L"Kazahook")) sConsumers |= KAZAGURU; + + if (::GetModuleHandleW(L"TextExtractorImpl32") || + ::GetModuleHandleW(L"TextExtractorImpl64")) + sConsumers |= YOUDAO; + + if (::GetModuleHandleW(L"uiautomation") || + ::GetModuleHandleW(L"uiautomationcore")) + sConsumers |= UIAUTOMATION; + + if (::GetModuleHandleW(L"AccEventCache")) { + sConsumers |= VISPEROSHARED; + } + + // If we have a known consumer remove the unknown bit. + if (sConsumers != Compatibility::UNKNOWN) + sConsumers &= ~Compatibility::UNKNOWN; +} + +/* static */ +bool Compatibility::HasKnownNonUiaConsumer() { + InitConsumers(); + return sConsumers & ~(Compatibility::UNKNOWN | UIAUTOMATION); +} + +void Compatibility::Init() { + // Note we collect some AT statistics/telemetry here for convenience. + InitConsumers(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::AccessibilityInProcClient, + nsPrintfCString("0x%X", sConsumers)); + + // Gather telemetry + uint32_t temp = sConsumers; + for (int i = 0; temp; i++) { + if (temp & 0x1) statistics::A11yConsumers(i); + + temp >>= 1; + } + + // Turn off new tab switching for Jaws and WE. + if (sConsumers & (JAWS | OLDJAWS | WE)) { + // Check to see if the pref for disallowing CtrlTab is already set. If so, + // bail out (respect the user settings). If not, set it. + if (!Preferences::HasUserValue("browser.ctrlTab.disallowForScreenReaders")) + Preferences::SetBool("browser.ctrlTab.disallowForScreenReaders", true); + } + + // If we have a consumer who is not NVDA, we enable detection for the + // InSendMessageEx compatibility hack. NVDA does not require this. + // We also skip UIA, as we see crashes there. + if ((sConsumers & (~(UIAUTOMATION | NVDA))) && BrowserTabsRemoteAutostart()) { + sUser32Interceptor.Init("user32.dll"); + sInSendMessageExStub.Set(sUser32Interceptor, "InSendMessageEx", + &InSendMessageExHook); + + // The vectored exception handler allows us to catch exceptions ahead of any + // SEH handlers. + if (!sVectoredExceptionHandler) { + // We need to let ASan's ShadowExceptionHandler remain in the firstHandler + // position, otherwise we'll get infinite recursion when our handler + // faults on shadow memory. + const ULONG firstHandler = FALSE; + sVectoredExceptionHandler = AddVectoredExceptionHandler( + firstHandler, &DetectInSendMessageExCompat); + } + } +} + +#if !defined(HAVE_64BIT_BUILD) + +static bool ReadCOMRegDefaultString(const nsString& aRegPath, + nsAString& aOutBuf) { + aOutBuf.Truncate(); + + nsAutoString fullyQualifiedRegPath; + fullyQualifiedRegPath.AppendLiteral(u"SOFTWARE\\Classes\\"); + fullyQualifiedRegPath.Append(aRegPath); + + // Get the required size and type of the registry value. + // We expect either REG_SZ or REG_EXPAND_SZ. + DWORD type; + DWORD bufLen = 0; + LONG result = ::RegGetValue(HKEY_LOCAL_MACHINE, fullyQualifiedRegPath.get(), + nullptr, RRF_RT_ANY, &type, nullptr, &bufLen); + if (result != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ)) { + return false; + } + + // Now obtain the value + DWORD flags = type == REG_SZ ? RRF_RT_REG_SZ : RRF_RT_REG_EXPAND_SZ; + + aOutBuf.SetLength((bufLen + 1) / sizeof(char16_t)); + + result = + ::RegGetValue(HKEY_LOCAL_MACHINE, fullyQualifiedRegPath.get(), nullptr, + flags, nullptr, aOutBuf.BeginWriting(), &bufLen); + if (result != ERROR_SUCCESS) { + aOutBuf.Truncate(); + return false; + } + + // Truncate terminator + aOutBuf.Truncate((bufLen + 1) / sizeof(char16_t) - 1); + return true; +} + +static bool IsSystemOleAcc(nsCOMPtr<nsIFile>& aFile) { + // Use FOLDERID_SystemX86 so that Windows doesn't give us a redirected + // system32 if we're a 32-bit process running on a 64-bit OS. This is + // necessary because the values that we are reading from the registry + // are not redirected; they reference SysWOW64 directly. + PWSTR systemPath = nullptr; + HRESULT hr = + ::SHGetKnownFolderPath(FOLDERID_SystemX86, 0, nullptr, &systemPath); + if (FAILED(hr)) { + return false; + } + + nsCOMPtr<nsIFile> oleAcc; + nsresult rv = NS_NewLocalFile(nsDependentString(systemPath), false, + getter_AddRefs(oleAcc)); + + ::CoTaskMemFree(systemPath); + systemPath = nullptr; + + if (NS_FAILED(rv)) { + return false; + } + + rv = oleAcc->Append(u"oleacc.dll"_ns); + if (NS_FAILED(rv)) { + return false; + } + + bool isEqual; + rv = oleAcc->Equals(aFile, &isEqual); + return NS_SUCCEEDED(rv) && isEqual; +} + +static bool IsTypelibPreferred() { + // If IAccessible's Proxy/Stub CLSID is kUniversalMarshalerClsid, then any + // external a11y clients are expecting to use a typelib. + constexpr auto kUniversalMarshalerClsid = + u"{00020424-0000-0000-C000-000000000046}"_ns; + + constexpr auto kIAccessiblePSClsidPath = + "Interface\\{618736E0-3C3D-11CF-810C-00AA00389B71}" + u"\\ProxyStubClsid32"_ns; + + nsAutoString psClsid; + if (!ReadCOMRegDefaultString(kIAccessiblePSClsidPath, psClsid)) { + return false; + } + + return psClsid.Equals(kUniversalMarshalerClsid, + nsCaseInsensitiveStringComparator); +} + +static bool IsIAccessibleTypelibRegistered() { + // The system default IAccessible typelib is always registered with version + // 1.1, under the neutral locale (LCID 0). + constexpr auto kIAccessibleTypelibRegPath = + u"TypeLib\\{1EA4DBF0-3C3B-11CF-810C-00AA00389B71}\\1.1\\0\\win32"_ns; + + nsAutoString typelibPath; + if (!ReadCOMRegDefaultString(kIAccessibleTypelibRegPath, typelibPath)) { + return false; + } + + nsCOMPtr<nsIFile> libTestFile; + nsresult rv = + NS_NewLocalFile(typelibPath, false, getter_AddRefs(libTestFile)); + if (NS_FAILED(rv)) { + return false; + } + + return IsSystemOleAcc(libTestFile); +} + +static bool IsIAccessiblePSRegistered() { + constexpr auto kIAccessiblePSRegPath = + u"CLSID\\{03022430-ABC4-11D0-BDE2-00AA001A1953}\\InProcServer32"_ns; + + nsAutoString proxyStubPath; + if (!ReadCOMRegDefaultString(kIAccessiblePSRegPath, proxyStubPath)) { + return false; + } + + nsCOMPtr<nsIFile> libTestFile; + nsresult rv = + NS_NewLocalFile(proxyStubPath, false, getter_AddRefs(libTestFile)); + if (NS_FAILED(rv)) { + return false; + } + + return IsSystemOleAcc(libTestFile); +} + +static bool UseIAccessibleProxyStub() { + // If a typelib is preferred then external clients are expecting to use + // typelib marshaling, so we should use that whenever available. + if (IsTypelibPreferred() && IsIAccessibleTypelibRegistered()) { + return false; + } + + // Otherwise we try the proxy/stub + if (IsIAccessiblePSRegistered()) { + return true; + } + + // If we reach this point then something is seriously wrong with the + // IAccessible configuration in the computer's registry. Let's annotate this + // so that we can easily determine this condition during crash analysis. + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IAccessibleConfig, "NoSystemTypeLibOrPS"_ns); + return false; +} + +#endif // !defined(HAVE_64BIT_BUILD) + +uint16_t Compatibility::GetActCtxResourceId() { +#if defined(HAVE_64BIT_BUILD) + // The manifest for 64-bit Windows is embedded with resource ID 64. + return 64; +#else + // The manifest for 32-bit Windows is embedded with resource ID 32. + // Beginning with Windows 10 Creators Update, 32-bit builds always use the + // 64-bit manifest. Older builds of Windows may or may not require the 64-bit + // manifest: UseIAccessibleProxyStub() determines the course of action. + if (mozilla::IsWin10CreatorsUpdateOrLater() || UseIAccessibleProxyStub()) { + return 64; + } + + return 32; +#endif // defined(HAVE_64BIT_BUILD) +} + +// static +void Compatibility::GetHumanReadableConsumersStr(nsAString& aResult) { + bool appened = false; + uint32_t index = 0; + for (uint32_t consumers = sConsumers; consumers; consumers = consumers >> 1) { + if (consumers & 0x1) { + if (appened) { + aResult.AppendLiteral(","); + } + aResult.Append(ConsumerStringMap[index]); + appened = true; + } + if (++index > CONSUMERS_ENUM_LEN) { + break; + } + } +} diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h new file mode 100644 index 0000000000..6e57fa5bec --- /dev/null +++ b/accessible/windows/msaa/Compatibility.h @@ -0,0 +1,128 @@ +/* -*- 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 COMPATIBILITY_MANAGER_H +#define COMPATIBILITY_MANAGER_H + +#include "mozilla/Maybe.h" +#include "nsString.h" +#include <stdint.h> + +namespace mozilla { +namespace a11y { + +/** + * Used to get compatibility modes. Note, modes are computed at accessibility + * start up time and aren't changed during lifetime. + */ +class Compatibility { + public: + /** + * Return true if JAWS mode is enabled. + */ + static bool IsJAWS() { return !!(sConsumers & (JAWS | OLDJAWS)); } + + /** + * Return true if using an e10s incompatible Jaws. + */ + static bool IsOldJAWS() { return !!(sConsumers & OLDJAWS); } + + /** + * Return true if WE mode is enabled. + */ + static bool IsWE() { return !!(sConsumers & WE); } + + /** + * Return true if Dolphin mode is enabled. + */ + static bool IsDolphin() { return !!(sConsumers & DOLPHIN); } + + /** + * Return true if JAWS, ZoomText or ZoomText Fusion 2021 or later is being + * used. These products share common code for interacting with Firefox and + * all require window emulation to be enabled. + */ + static bool IsVisperoShared() { return !!(sConsumers & VISPEROSHARED); } + + /** + * @return ID of a11y manifest resource to be passed to + * mscom::ActivationContext + */ + static uint16_t GetActCtxResourceId(); + + /** + * Return a string describing sConsumers suitable for about:support. + * Exposed through nsIXULRuntime.accessibilityInstantiator. + */ + static void GetHumanReadableConsumersStr(nsAString& aResult); + + /** + * Initialize compatibility mode information. + */ + static void Init(); + + static Maybe<bool> OnUIAMessage(WPARAM aWParam, LPARAM aLParam); + + static Maybe<DWORD> GetUiaRemotePid() { return sUiaRemotePid; } + + /** + * return true if a known, non-UIA a11y consumer is present + */ + static bool HasKnownNonUiaConsumer(); + + /** + * Return true if a module's version is lesser than the given version. + * Generally, the version should be provided using the MAKE_FILE_VERSION + * macro. + * If the version information cannot be retrieved, true is returned; i.e. + * no version information implies an earlier version. + */ + static bool IsModuleVersionLessThan(HMODULE aModuleHandle, + unsigned long long aVersion); + + private: + Compatibility(); + Compatibility(const Compatibility&); + Compatibility& operator=(const Compatibility&); + + static void InitConsumers(); + + /** + * List of detected consumers of a11y (used for statistics/telemetry and + * compat) + */ + enum { + NVDA = 1 << 0, + JAWS = 1 << 1, + OLDJAWS = 1 << 2, + WE = 1 << 3, + DOLPHIN = 1 << 4, + SEROTEK = 1 << 5, + COBRA = 1 << 6, + ZOOMTEXT = 1 << 7, + KAZAGURU = 1 << 8, + YOUDAO = 1 << 9, + UNKNOWN = 1 << 10, + UIAUTOMATION = 1 << 11, + VISPEROSHARED = 1 << 12 + }; +#define CONSUMERS_ENUM_LEN 13 + + private: + static uint32_t sConsumers; + static Maybe<DWORD> sUiaRemotePid; +}; + +} // namespace a11y +} // namespace mozilla + +// Convert the 4 (decimal) components of a DLL version number into a +// single unsigned long long, as needed by +// mozilla::a11y::Compatibility::IsModuleVersionLessThan. +#define MAKE_FILE_VERSION(a, b, c, d) \ + ((a##ULL << 48) + (b##ULL << 32) + (c##ULL << 16) + d##ULL) + +#endif diff --git a/accessible/windows/msaa/CompatibilityUIA.cpp b/accessible/windows/msaa/CompatibilityUIA.cpp new file mode 100644 index 0000000000..547d997ae0 --- /dev/null +++ b/accessible/windows/msaa/CompatibilityUIA.cpp @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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 "Compatibility.h" + +#include "mozilla/ScopeExit.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/WindowsVersion.h" + +#include "nsDataHashtable.h" +#include "nsPrintfCString.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsUnicharUtils.h" +#include "nsWinUtils.h" + +#include "NtUndoc.h" + +#if defined(UIA_LOGGING) + +# define LOG_ERROR(FuncName) \ + { \ + DWORD err = ::GetLastError(); \ + nsPrintfCString msg(#FuncName " failed with code %u\n", err); \ + ::OutputDebugStringA(msg.get()); \ + } + +#else + +# define LOG_ERROR(FuncName) + +#endif // defined(UIA_LOGGING) + +struct ByteArrayDeleter { + void operator()(void* aBuf) { delete[] reinterpret_cast<char*>(aBuf); } +}; + +typedef UniquePtr<OBJECT_DIRECTORY_INFORMATION, ByteArrayDeleter> ObjDirInfoPtr; + +// ComparatorFnT returns true to continue searching, or else false to indicate +// search completion. +template <typename ComparatorFnT> +static bool FindNamedObject(const ComparatorFnT& aComparator) { + // We want to enumerate every named kernel object in our session. We do this + // by opening a directory object using a path constructed using the session + // id under which our process resides. + DWORD sessionId; + if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &sessionId)) { + return false; + } + + nsAutoString path; + path.AppendPrintf("\\Sessions\\%u\\BaseNamedObjects", sessionId); + + UNICODE_STRING baseNamedObjectsName; + ::RtlInitUnicodeString(&baseNamedObjectsName, path.get()); + + OBJECT_ATTRIBUTES attributes; + InitializeObjectAttributes(&attributes, &baseNamedObjectsName, 0, nullptr, + nullptr); + + HANDLE rawBaseNamedObjects; + NTSTATUS ntStatus = ::NtOpenDirectoryObject( + &rawBaseNamedObjects, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &attributes); + if (!NT_SUCCESS(ntStatus)) { + return false; + } + + nsAutoHandle baseNamedObjects(rawBaseNamedObjects); + + ULONG context = 0, returnedLen; + + ULONG objDirInfoBufLen = 1024 * sizeof(OBJECT_DIRECTORY_INFORMATION); + ObjDirInfoPtr objDirInfo(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>( + new char[objDirInfoBufLen])); + + // Now query that directory object for every named object that it contains. + + BOOL firstCall = TRUE; + + do { + ntStatus = ::NtQueryDirectoryObject(baseNamedObjects, objDirInfo.get(), + objDirInfoBufLen, FALSE, firstCall, + &context, &returnedLen); +#if defined(HAVE_64BIT_BUILD) + if (!NT_SUCCESS(ntStatus)) { + return false; + } +#else + if (ntStatus == STATUS_BUFFER_TOO_SMALL) { + // This case only occurs on 32-bit builds running atop WOW64. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1423999#c3) + objDirInfo.reset(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>( + new char[returnedLen])); + objDirInfoBufLen = returnedLen; + continue; + } else if (!NT_SUCCESS(ntStatus)) { + return false; + } +#endif + + // NtQueryDirectoryObject gave us an array of OBJECT_DIRECTORY_INFORMATION + // structures whose final entry is zeroed out. + OBJECT_DIRECTORY_INFORMATION* curDir = objDirInfo.get(); + while (curDir->mName.Length && curDir->mTypeName.Length) { + // We use nsDependentSubstring here because UNICODE_STRINGs are not + // guaranteed to be null-terminated. + nsDependentSubstring objName(curDir->mName.Buffer, + curDir->mName.Length / sizeof(wchar_t)); + nsDependentSubstring typeName(curDir->mTypeName.Buffer, + curDir->mTypeName.Length / sizeof(wchar_t)); + + if (!aComparator(objName, typeName)) { + return true; + } + + ++curDir; + } + + firstCall = FALSE; + } while (ntStatus == STATUS_MORE_ENTRIES); + + return false; +} + +static const char* gBlockedUiaClients[] = {"osk.exe"}; + +static bool ShouldBlockUIAClient(nsIFile* aClientExe) { + if (PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) { + return false; + } + + nsAutoString leafName; + nsresult rv = aClientExe->GetLeafName(leafName); + if (NS_FAILED(rv)) { + return false; + } + + for (size_t index = 0, len = ArrayLength(gBlockedUiaClients); index < len; + ++index) { + if (leafName.EqualsIgnoreCase(gBlockedUiaClients[index])) { + return true; + } + } + + return false; +} + +namespace mozilla { +namespace a11y { + +Maybe<DWORD> Compatibility::sUiaRemotePid; + +Maybe<bool> Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) { + auto clearUiaRemotePid = MakeScopeExit([]() { sUiaRemotePid = Nothing(); }); + + Telemetry::AutoTimer<Telemetry::A11Y_UIA_DETECTION_TIMING_MS> timer; + + // UIA creates a section containing the substring "HOOK_SHMEM_" + constexpr auto kStrHookShmem = u"HOOK_SHMEM_"_ns; + + // The section name always ends with this suffix, which is derived from the + // current thread id and the UIA message's WPARAM and LPARAM. + nsAutoString partialSectionSuffix; + partialSectionSuffix.AppendPrintf("_%08x_%08x_%08x", ::GetCurrentThreadId(), + static_cast<DWORD>(aLParam), aWParam); + + // Find any named Section that matches the naming convention of the UIA shared + // memory. + nsAutoHandle section; + auto comparator = [&](const nsDependentSubstring& aName, + const nsDependentSubstring& aType) -> bool { + if (aType.Equals(u"Section"_ns) && FindInReadable(kStrHookShmem, aName) && + StringEndsWith(aName, partialSectionSuffix)) { + section.own(::OpenFileMapping(GENERIC_READ, FALSE, + PromiseFlatString(aName).get())); + return false; + } + + return true; + }; + + if (!FindNamedObject(comparator) || !section) { + return Nothing(); + } + + NTSTATUS ntStatus; + + // First we must query for a list of all the open handles in the system. + UniquePtr<char[]> handleInfoBuf; + ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) + + 1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX); + + // We must query for handle information in a loop, since we are effectively + // asking the kernel to take a snapshot of all the handles on the system; + // the size of the required buffer may fluctuate between successive calls. + while (true) { + // These allocations can be hundreds of megabytes on some computers, so + // we should use fallible new here. + handleInfoBuf = MakeUniqueFallible<char[]>(handleInfoBufLen); + if (!handleInfoBuf) { + return Nothing(); + } + + ntStatus = ::NtQuerySystemInformation( + (SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation, + handleInfoBuf.get(), handleInfoBufLen, &handleInfoBufLen); + if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) { + continue; + } + + if (!NT_SUCCESS(ntStatus)) { + return Nothing(); + } + + break; + } + + const DWORD ourPid = ::GetCurrentProcessId(); + Maybe<PVOID> kernelObject; + static Maybe<USHORT> sectionObjTypeIndex; + nsTHashtable<nsUint32HashKey> nonSectionObjTypes; + nsDataHashtable<nsVoidPtrHashKey, DWORD> objMap; + + auto handleInfo = + reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(handleInfoBuf.get()); + + for (ULONG index = 0; index < handleInfo->mHandleCount; ++index) { + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX& curHandle = handleInfo->mHandles[index]; + + HANDLE handle = reinterpret_cast<HANDLE>(curHandle.mHandle); + + // The mapping of the curHandle.mObjectTypeIndex field depends on the + // underlying OS kernel. As we scan through the handle list, we record the + // type indices such that we may use those values to skip over handles that + // refer to non-section objects. + if (sectionObjTypeIndex) { + // If we know the type index for Sections, that's the fastest check... + if (sectionObjTypeIndex.value() != curHandle.mObjectTypeIndex) { + // Not a section + continue; + } + } else if (nonSectionObjTypes.Contains( + static_cast<uint32_t>(curHandle.mObjectTypeIndex))) { + // Otherwise we check whether or not the object type is definitely _not_ + // a Section... + continue; + } else if (ourPid == curHandle.mPid) { + // Otherwise we need to issue some system calls to find out the object + // type corresponding to the current handle's type index. + ULONG objTypeBufLen; + ntStatus = ::NtQueryObject(handle, ObjectTypeInformation, nullptr, 0, + &objTypeBufLen); + if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) { + continue; + } + + auto objTypeBuf = MakeUnique<char[]>(objTypeBufLen); + ntStatus = + ::NtQueryObject(handle, ObjectTypeInformation, objTypeBuf.get(), + objTypeBufLen, &objTypeBufLen); + if (!NT_SUCCESS(ntStatus)) { + continue; + } + + auto objType = + reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get()); + + // Now we check whether the object's type name matches "Section" + nsDependentSubstring objTypeName( + objType->TypeName.Buffer, objType->TypeName.Length / sizeof(wchar_t)); + if (!objTypeName.Equals(u"Section"_ns)) { + nonSectionObjTypes.PutEntry( + static_cast<uint32_t>(curHandle.mObjectTypeIndex)); + continue; + } + + sectionObjTypeIndex = Some(curHandle.mObjectTypeIndex); + } + + // At this point we know that curHandle references a Section object. + // Now we can do some actual tests on it. + + if (ourPid != curHandle.mPid) { + if (kernelObject && kernelObject.value() == curHandle.mObject) { + // The kernel objects match -- we have found the remote pid! + sUiaRemotePid = Some(curHandle.mPid); + break; + } + + // An object that is not ours. Since we do not yet know which kernel + // object we're interested in, we'll save the current object for later. + objMap.Put(curHandle.mObject, curHandle.mPid); + } else if (handle == section.get()) { + // This is the file mapping that we opened above. We save this mObject + // in order to compare to Section objects opened by other processes. + kernelObject = Some(curHandle.mObject); + } + } + + if (!kernelObject) { + return Nothing(); + } + + if (!sUiaRemotePid) { + // We found kernelObject *after* we saw the remote process's copy. Now we + // must look it up in objMap. + DWORD pid; + if (objMap.Get(kernelObject.value(), &pid)) { + sUiaRemotePid = Some(pid); + } + } + + if (!sUiaRemotePid) { + return Nothing(); + } + + a11y::SetInstantiator(sUiaRemotePid.value()); + + // Block if necessary + nsCOMPtr<nsIFile> instantiator; + if (a11y::GetInstantiator(getter_AddRefs(instantiator)) && + ShouldBlockUIAClient(instantiator)) { + return Some(false); + } + + return Some(true); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/windows/msaa/DocAccessibleWrap.cpp b/accessible/windows/msaa/DocAccessibleWrap.cpp new file mode 100644 index 0000000000..c4d696ae3c --- /dev/null +++ b/accessible/windows/msaa/DocAccessibleWrap.cpp @@ -0,0 +1,210 @@ +/* -*- 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 "DocAccessibleWrap.h" + +#include "Compatibility.h" +#include "mozilla/PresShell.h" +#include "mozilla/dom/BrowserChild.h" +#include "DocAccessibleChild.h" +#include "nsWinUtils.h" +#include "Role.h" +#include "RootAccessible.h" +#include "sdnDocAccessible.h" +#include "Statistics.h" + +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// DocAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +DocAccessibleWrap::DocAccessibleWrap(dom::Document* aDocument, + PresShell* aPresShell) + : DocAccessible(aDocument, aPresShell), mHWND(nullptr) {} + +DocAccessibleWrap::~DocAccessibleWrap() {} + +IMPL_IUNKNOWN_QUERY_HEAD(DocAccessibleWrap) +if (aIID == IID_ISimpleDOMDocument) { + statistics::ISimpleDOMUsed(); + *aInstancePtr = static_cast<ISimpleDOMDocument*>(new sdnDocAccessible(this)); + static_cast<IUnknown*>(*aInstancePtr)->AddRef(); + return S_OK; +} +IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(HyperTextAccessibleWrap) + +STDMETHODIMP +DocAccessibleWrap::get_accParent( + /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) { + if (IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + // We might be a top-level document in a content process. + DocAccessibleChild* ipcDoc = IPCDoc(); + if (ipcDoc && static_cast<dom::BrowserChild*>(ipcDoc->Manager()) + ->GetTopLevelDocAccessibleChild() == ipcDoc) { + // Emulated window proxy is only set for the top level content document when + // emulation is enabled. + RefPtr<IDispatch> dispParent = ipcDoc->GetEmulatedWindowIAccessible(); + if (!dispParent) { + dispParent = ipcDoc->GetParentIAccessible(); + } + + if (!dispParent) { + return S_FALSE; + } + + dispParent.forget(ppdispParent); + return S_OK; + } + + // In the parent process, return window system accessible object for root + // document accessibles, as well as tab document accessibles if window + // emulation is enabled. + if (XRE_IsParentProcess() && + (!ParentDocument() || + (nsWinUtils::IsWindowEmulationStarted() && + nsCoreUtils::IsTopLevelContentDocInProcess(DocumentNode())))) { + HWND hwnd = static_cast<HWND>(GetNativeWindow()); + if (hwnd && !ParentDocument()) { + nsIFrame* frame = GetFrame(); + if (frame) { + nsIWidget* widget = frame->GetNearestWidget(); + if (widget->WindowType() == eWindowType_child && !widget->GetParent()) { + // Bug 1427304: Windows opened with popup=yes (such as the WebRTC + // sharing indicator) get two HWNDs. The root widget is associated + // with the inner HWND, but the outer HWND still answers to + // WM_GETOBJECT queries. This means that getting the parent of the + // oleacc window accessible for the inner HWND returns this + // root accessible. Thus, to avoid a loop, we must never return the + // oleacc window accessible for the inner HWND. Instead, we use the + // outer HWND here. + HWND parentHwnd = ::GetParent(hwnd); + if (parentHwnd) { + MOZ_ASSERT(::GetWindowLongW(parentHwnd, GWL_STYLE) & WS_POPUP, + "Parent HWND should be a popup!"); + hwnd = parentHwnd; + } + } + } + } + if (hwnd && + SUCCEEDED(::AccessibleObjectFromWindow( + hwnd, OBJID_WINDOW, IID_IAccessible, (void**)ppdispParent))) { + return S_OK; + } + } + + return DocAccessible::get_accParent(ppdispParent); +} + +STDMETHODIMP +DocAccessibleWrap::get_accValue(VARIANT aVarChild, BSTR __RPC_FAR* aValue) { + if (!aValue) return E_INVALIDARG; + *aValue = nullptr; + + // For backwards-compat, we still support old MSAA hack to provide URL in + // accValue Check for real value first + HRESULT hr = AccessibleWrap::get_accValue(aVarChild, aValue); + if (FAILED(hr) || *aValue || aVarChild.lVal != CHILDID_SELF) return hr; + + // If document is being used to create a widget, don't use the URL hack + roles::Role role = Role(); + if (role != roles::DOCUMENT && role != roles::APPLICATION && + role != roles::DIALOG && role != roles::ALERT && + role != roles::NON_NATIVE_DOCUMENT) + return hr; + + nsAutoString url; + URL(url); + if (url.IsEmpty()) return S_FALSE; + + *aValue = ::SysAllocStringLen(url.get(), url.Length()); + return *aValue ? S_OK : E_OUTOFMEMORY; +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessible + +void DocAccessibleWrap::Shutdown() { + // Do window emulation specific shutdown if emulation was started. + if (nsWinUtils::IsWindowEmulationStarted()) { + // Destroy window created for root document. + if (mDocFlags & eTopLevelContentDocInProcess) { + MOZ_ASSERT(XRE_IsParentProcess()); + HWND hWnd = static_cast<HWND>(mHWND); + ::RemovePropW(hWnd, kPropNameDocAcc); + ::DestroyWindow(hWnd); + } + + mHWND = nullptr; + } + + DocAccessible::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// DocAccessible public + +void* DocAccessibleWrap::GetNativeWindow() const { + if (XRE_IsContentProcess()) { + DocAccessibleChild* ipcDoc = IPCDoc(); + if (!ipcDoc) { + return nullptr; + } + + return ipcDoc->GetNativeWindowHandle(); + } else if (mHWND) { + return mHWND; + } + return DocAccessible::GetNativeWindow(); +} + +//////////////////////////////////////////////////////////////////////////////// +// DocAccessible protected + +void DocAccessibleWrap::DoInitialUpdate() { + DocAccessible::DoInitialUpdate(); + + if (nsWinUtils::IsWindowEmulationStarted()) { + // Create window for tab document. + if (mDocFlags & eTopLevelContentDocInProcess) { + MOZ_ASSERT(XRE_IsParentProcess()); + a11y::RootAccessible* rootDocument = RootAccessible(); + bool isActive = true; + nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0); + if (Compatibility::IsDolphin()) { + rect = Bounds(); + nsIntRect rootRect = rootDocument->Bounds(); + rect.MoveToX(rootRect.X() - rect.X()); + rect.MoveByY(-rootRect.Y()); + + auto* bc = mDocumentNode->GetBrowsingContext(); + isActive = bc && bc->IsActive(); + } + + RefPtr<DocAccessibleWrap> self(this); + nsWinUtils::NativeWindowCreateProc onCreate([self](HWND aHwnd) -> void { + ::SetPropW(aHwnd, kPropNameDocAcc, + reinterpret_cast<HANDLE>(self.get())); + }); + + HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow()); + mHWND = nsWinUtils::CreateNativeWindow( + kClassNameTabContent, parentWnd, rect.X(), rect.Y(), rect.Width(), + rect.Height(), isActive, &onCreate); + } else { + DocAccessible* parentDocument = ParentDocument(); + if (parentDocument) mHWND = parentDocument->GetNativeWindow(); + } + } +} diff --git a/accessible/windows/msaa/DocAccessibleWrap.h b/accessible/windows/msaa/DocAccessibleWrap.h new file mode 100644 index 0000000000..f37e7be6d2 --- /dev/null +++ b/accessible/windows/msaa/DocAccessibleWrap.h @@ -0,0 +1,70 @@ +/* -*- 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_DocAccessibleWrap_h__ +#define mozilla_a11y_DocAccessibleWrap_h__ + +#include "DocAccessible.h" + +namespace mozilla { + +class PresShell; + +namespace a11y { + +class DocAccessibleWrap : public DocAccessible { + public: + DocAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell); + virtual ~DocAccessibleWrap(); + + DECL_IUNKNOWN_INHERITED + + // IAccessible + + // Override get_accParent for e10s + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accParent( + /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) + override; + + // Override get_accValue to provide URL when no other value is available + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszValue) override; + + // Accessible + virtual void Shutdown(); + + // DocAccessible + virtual void* GetNativeWindow() const; + + /** + * Manage the mapping from id to Accessible. + */ + void AddID(uint32_t aID, AccessibleWrap* aAcc) { + mIDToAccessibleMap.Put(aID, aAcc); + } + void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); } + AccessibleWrap* GetAccessibleByID(uint32_t aID) const { + return mIDToAccessibleMap.Get(aID); + } + + protected: + // DocAccessible + virtual void DoInitialUpdate(); + + protected: + void* mHWND; + + /* + * This provides a mapping from 32 bit id to accessible objects. + */ + nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/EnumVariant.cpp b/accessible/windows/msaa/EnumVariant.cpp new file mode 100644 index 0000000000..46e1ccd88c --- /dev/null +++ b/accessible/windows/msaa/EnumVariant.cpp @@ -0,0 +1,85 @@ +/* -*- 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 "EnumVariant.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// ChildrenEnumVariant +//////////////////////////////////////////////////////////////////////////////// + +IMPL_IUNKNOWN_QUERY_HEAD(ChildrenEnumVariant) +IMPL_IUNKNOWN_QUERY_IFACE(IEnumVARIANT) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAnchorAcc) + +STDMETHODIMP +ChildrenEnumVariant::Next(ULONG aCount, VARIANT FAR* aItems, + ULONG FAR* aCountFetched) { + if (!aItems || !aCountFetched) return E_INVALIDARG; + + *aCountFetched = 0; + + if (mAnchorAcc->IsDefunct() || mAnchorAcc->GetChildAt(mCurIndex) != mCurAcc) + return CO_E_OBJNOTCONNECTED; + + ULONG countFetched = 0; + while (mCurAcc && countFetched < aCount) { + VariantInit(aItems + countFetched); + + IDispatch* accNative = AccessibleWrap::NativeAccessible(mCurAcc); + + ++mCurIndex; + mCurAcc = mAnchorAcc->GetChildAt(mCurIndex); + + // Don't output the accessible and count it as having been fetched unless + // it is non-null + MOZ_ASSERT(accNative); + if (!accNative) { + continue; + } + + aItems[countFetched].pdispVal = accNative; + aItems[countFetched].vt = VT_DISPATCH; + ++countFetched; + } + + (*aCountFetched) = countFetched; + + return countFetched < aCount ? S_FALSE : S_OK; +} + +STDMETHODIMP +ChildrenEnumVariant::Skip(ULONG aCount) { + if (mAnchorAcc->IsDefunct() || mAnchorAcc->GetChildAt(mCurIndex) != mCurAcc) + return CO_E_OBJNOTCONNECTED; + + mCurIndex += aCount; + mCurAcc = mAnchorAcc->GetChildAt(mCurIndex); + + return mCurAcc ? S_OK : S_FALSE; +} + +STDMETHODIMP +ChildrenEnumVariant::Reset() { + if (mAnchorAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + mCurIndex = 0; + mCurAcc = mAnchorAcc->GetChildAt(0); + + return S_OK; +} + +STDMETHODIMP +ChildrenEnumVariant::Clone(IEnumVARIANT** aEnumVariant) { + if (!aEnumVariant) return E_INVALIDARG; + + *aEnumVariant = new ChildrenEnumVariant(*this); + (*aEnumVariant)->AddRef(); + + return S_OK; +} diff --git a/accessible/windows/msaa/EnumVariant.h b/accessible/windows/msaa/EnumVariant.h new file mode 100644 index 0000000000..bcf330718f --- /dev/null +++ b/accessible/windows/msaa/EnumVariant.h @@ -0,0 +1,60 @@ +/* -*- 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_EnumVariant_h__ +#define mozilla_a11y_EnumVariant_h__ + +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" + +namespace mozilla { +namespace a11y { + +/** + * Used to fetch accessible children. + */ +class ChildrenEnumVariant final : public IEnumVARIANT { + public: + explicit ChildrenEnumVariant(AccessibleWrap* aAnchor) + : mAnchorAcc(aAnchor), mCurAcc(mAnchorAcc->GetChildAt(0)), mCurIndex(0) {} + + // IUnknown + DECL_IUNKNOWN + + // IEnumVariant + virtual /* [local] */ HRESULT STDMETHODCALLTYPE Next( + /* [in] */ ULONG aCount, + /* [length_is][size_is][out] */ VARIANT* aItems, + /* [out] */ ULONG* aCountFetched); + + virtual HRESULT STDMETHODCALLTYPE Skip( + /* [in] */ ULONG aCount); + + virtual HRESULT STDMETHODCALLTYPE Reset(); + + virtual HRESULT STDMETHODCALLTYPE Clone( + /* [out] */ IEnumVARIANT** aEnumVaraint); + + private: + ChildrenEnumVariant() = delete; + ChildrenEnumVariant& operator=(const ChildrenEnumVariant&) = delete; + + ChildrenEnumVariant(const ChildrenEnumVariant& aEnumVariant) + : mAnchorAcc(aEnumVariant.mAnchorAcc), + mCurAcc(aEnumVariant.mCurAcc), + mCurIndex(aEnumVariant.mCurIndex) {} + virtual ~ChildrenEnumVariant() {} + + protected: + RefPtr<AccessibleWrap> mAnchorAcc; + Accessible* mCurAcc; + uint32_t mCurIndex; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/GeckoCustom.cpp b/accessible/windows/msaa/GeckoCustom.cpp new file mode 100644 index 0000000000..8facb84c0b --- /dev/null +++ b/accessible/windows/msaa/GeckoCustom.cpp @@ -0,0 +1,68 @@ +/* -*- 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 "GeckoCustom.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +IMPL_IUNKNOWN_QUERY_HEAD(GeckoCustom) +IMPL_IUNKNOWN_QUERY_IFACE(IGeckoCustom) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAcc) + +HRESULT +GeckoCustom::get_anchorCount(long* aCount) { + *aCount = mAcc->AnchorCount(); + return S_OK; +} + +HRESULT +GeckoCustom::get_boundsInCSSPixels(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + nsIntRect bounds = mAcc->BoundsInCSSPixels(); + if (!bounds.IsEmpty()) { + *aX = bounds.X(); + *aY = bounds.Y(); + *aWidth = bounds.Width(); + *aHeight = bounds.Height(); + } + + return S_OK; +} + +HRESULT +GeckoCustom::get_DOMNodeID(BSTR* aID) { + nsIContent* content = mAcc->GetContent(); + if (!content) { + return S_OK; + } + + nsAtom* id = content->GetID(); + if (id) { + nsAutoString idStr; + id->ToString(idStr); + *aID = ::SysAllocStringLen(idStr.get(), idStr.Length()); + } + return S_OK; +} + +STDMETHODIMP +GeckoCustom::get_ID(uint64_t* aID) { + *aID = mAcc->IsDoc() ? 0 : reinterpret_cast<uintptr_t>(mAcc.get()); + return S_OK; +} + +STDMETHODIMP +GeckoCustom::get_minimumIncrement(double* aIncrement) { + *aIncrement = mAcc->Step(); + return S_OK; +} + +STDMETHODIMP +GeckoCustom::get_mozState(uint64_t* aState) { + *aState = mAcc->State(); + return S_OK; +} diff --git a/accessible/windows/msaa/GeckoCustom.h b/accessible/windows/msaa/GeckoCustom.h new file mode 100644 index 0000000000..a56e6d49bb --- /dev/null +++ b/accessible/windows/msaa/GeckoCustom.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_GeckoCustom_h_ +#define mozilla_a11y_GeckoCustom_h_ + +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" +#include "IGeckoCustom.h" + +namespace mozilla { +namespace a11y { + +/** + * a dumpster to put things exposed by the xpcom API but not a windows platform + * API for the purposes of testing. + */ +class GeckoCustom final : public IGeckoCustom { + public: + explicit GeckoCustom(AccessibleWrap* aAcc) : mAcc(aAcc) {} + + // IUnknown + DECL_IUNKNOWN + + virtual STDMETHODIMP get_anchorCount(long* aCount); + virtual STDMETHODIMP get_boundsInCSSPixels(int32_t* aX, int32_t* aY, + int32_t* aWidth, int32_t* aHeight); + virtual STDMETHODIMP get_DOMNodeID(BSTR* aID); + virtual STDMETHODIMP get_ID(uint64_t* aID); + virtual STDMETHODIMP get_minimumIncrement(double* aIncrement); + virtual STDMETHODIMP get_mozState(uint64_t* aState); + + private: + GeckoCustom() = delete; + GeckoCustom& operator=(const GeckoCustom&) = delete; + GeckoCustom(const GeckoCustom&) = delete; + GeckoCustom(GeckoCustom&&) = delete; + GeckoCustom& operator=(GeckoCustom&&) = delete; + + ~GeckoCustom() {} + + protected: + RefPtr<AccessibleWrap> mAcc; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/HTMLTableAccessibleWrap.cpp b/accessible/windows/msaa/HTMLTableAccessibleWrap.cpp new file mode 100644 index 0000000000..dd2e4b1efa --- /dev/null +++ b/accessible/windows/msaa/HTMLTableAccessibleWrap.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "HTMLTableAccessibleWrap.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableAccessibleWrap, HTMLTableAccessible) + +IMPL_IUNKNOWN_INHERITED2(HTMLTableAccessibleWrap, AccessibleWrap, + HyperTextAccessibleWrap, ia2AccessibleTable) + +void HTMLTableAccessibleWrap::Shutdown() { + ia2AccessibleTable::mTable = nullptr; + HTMLTableAccessible::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableCellAccessibleWrap, + HTMLTableCellAccessible) + +IMPL_IUNKNOWN_INHERITED1(HTMLTableCellAccessibleWrap, HyperTextAccessibleWrap, + ia2AccessibleTableCell) + +void HTMLTableCellAccessibleWrap::Shutdown() { + ia2AccessibleTableCell::mTableCell = nullptr; + HTMLTableCellAccessible::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableHeaderCellAccessibleWrap, + HTMLTableHeaderCellAccessible) + +IMPL_IUNKNOWN_INHERITED1(HTMLTableHeaderCellAccessibleWrap, + HyperTextAccessibleWrap, ia2AccessibleTableCell) + +void HTMLTableHeaderCellAccessibleWrap::Shutdown() { + ia2AccessibleTableCell::mTableCell = nullptr; + HTMLTableHeaderCellAccessible::Shutdown(); +} diff --git a/accessible/windows/msaa/HTMLTableAccessibleWrap.h b/accessible/windows/msaa/HTMLTableAccessibleWrap.h new file mode 100644 index 0000000000..9abee638b2 --- /dev/null +++ b/accessible/windows/msaa/HTMLTableAccessibleWrap.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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_HTMLTableAccessibleWrap_h__ +#define mozilla_a11y_HTMLTableAccessibleWrap_h__ + +#include "HTMLTableAccessible.h" + +#include "ia2AccessibleTable.h" +#include "ia2AccessibleTableCell.h" + +namespace mozilla { +namespace a11y { + +/** + * IA2 wrapper class for HTMLTableAccessible implementing IAccessibleTable + * and IAccessibleTable2 interfaces. + */ +class HTMLTableAccessibleWrap : public HTMLTableAccessible, + public ia2AccessibleTable { + ~HTMLTableAccessibleWrap() {} + + public: + HTMLTableAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : HTMLTableAccessible(aContent, aDoc), ia2AccessibleTable(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +/** + * IA2 wrapper class for HTMLTableCellAccessible implementing + * IAccessibleTableCell interface. + */ +class HTMLTableCellAccessibleWrap : public HTMLTableCellAccessible, + public ia2AccessibleTableCell { + ~HTMLTableCellAccessibleWrap() {} + + public: + HTMLTableCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : HTMLTableCellAccessible(aContent, aDoc), ia2AccessibleTableCell(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +/** + * IA2 wrapper class for HTMLTableHeaderCellAccessible implementing + * IAccessibleTableCell interface. + */ +class HTMLTableHeaderCellAccessibleWrap : public HTMLTableHeaderCellAccessible, + public ia2AccessibleTableCell { + ~HTMLTableHeaderCellAccessibleWrap() {} + + public: + HTMLTableHeaderCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : HTMLTableHeaderCellAccessible(aContent, aDoc), + ia2AccessibleTableCell(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp b/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp new file mode 100644 index 0000000000..ad5a536528 --- /dev/null +++ b/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "HTMLWin32ObjectAccessible.h" + +#include "Role.h" +#include "States.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLWin32ObjectOwnerAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLWin32ObjectOwnerAccessible::HTMLWin32ObjectOwnerAccessible( + nsIContent* aContent, DocAccessible* aDoc, void* aHwnd) + : AccessibleWrap(aContent, aDoc), mHwnd(aHwnd) { + mStateFlags |= eNoKidsFromDOM; + + // Our only child is a HTMLWin32ObjectAccessible object. + if (mHwnd) { + mNativeAccessible = new HTMLWin32ObjectAccessible(mHwnd, aDoc); + AppendChild(mNativeAccessible); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLWin32ObjectOwnerAccessible: Accessible implementation + +void HTMLWin32ObjectOwnerAccessible::Shutdown() { + AccessibleWrap::Shutdown(); + mNativeAccessible = nullptr; +} + +role HTMLWin32ObjectOwnerAccessible::NativeRole() const { + return roles::EMBEDDED_OBJECT; +} + +bool HTMLWin32ObjectOwnerAccessible::NativelyUnavailable() const { + // XXX: No HWND means this is windowless plugin which is not accessible in + // the meantime. + return !mHwnd; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLWin32ObjectAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLWin32ObjectAccessible::HTMLWin32ObjectAccessible(void* aHwnd, + DocAccessible* aDoc) + : DummyAccessible(aDoc) { + mHwnd = aHwnd; + if (mHwnd) { +#if defined(MOZ_SANDBOX) + if (XRE_IsContentProcess()) { + DocAccessibleChild* ipcDoc = aDoc->IPCDoc(); + MOZ_ASSERT(ipcDoc); + if (!ipcDoc) { + return; + } + + IAccessibleHolder proxyHolder; + if (!ipcDoc->SendGetWindowedPluginIAccessible( + reinterpret_cast<uintptr_t>(mHwnd), &proxyHolder)) { + return; + } + + mCOMProxy.reset(proxyHolder.Release()); + return; + } +#endif + + // The plugin is not windowless. In this situation we use + // use its inner child owned by the plugin so that we don't get + // in an infinite loop, where the WM_GETOBJECT's get forwarded + // back to us and create another HTMLWin32ObjectAccessible + mHwnd = ::GetWindow((HWND)aHwnd, GW_CHILD); + } +} + +void HTMLWin32ObjectAccessible::GetNativeInterface(void** aNativeAccessible) { +#if defined(MOZ_SANDBOX) + if (XRE_IsContentProcess()) { + RefPtr<IAccessible> addRefed = mCOMProxy.get(); + addRefed.forget(aNativeAccessible); + return; + } +#endif + + if (mHwnd) { + ::AccessibleObjectFromWindow(static_cast<HWND>(mHwnd), OBJID_WINDOW, + IID_IAccessible, aNativeAccessible); + } +} diff --git a/accessible/windows/msaa/HTMLWin32ObjectAccessible.h b/accessible/windows/msaa/HTMLWin32ObjectAccessible.h new file mode 100644 index 0000000000..5d660c962e --- /dev/null +++ b/accessible/windows/msaa/HTMLWin32ObjectAccessible.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_HTMLWin32ObjectAccessible_h_ +#define mozilla_a11y_HTMLWin32ObjectAccessible_h_ + +#include "BaseAccessibles.h" + +#if defined(MOZ_SANDBOX) +# include "mozilla/mscom/Ptr.h" +#endif + +struct IAccessible; + +namespace mozilla { +namespace a11y { + +class HTMLWin32ObjectOwnerAccessible : public AccessibleWrap { + public: + // This will own the HTMLWin32ObjectAccessible. We create this where the + // <object> or <embed> exists in the tree, so that get_accNextSibling() etc. + // will still point to Gecko accessible sibling content. This is necessary + // because the native plugin accessible doesn't know where it exists in the + // Mozilla tree, and returns null for previous and next sibling. This would + // have the effect of cutting off all content after the plugin. + HTMLWin32ObjectOwnerAccessible(nsIContent* aContent, DocAccessible* aDoc, + void* aHwnd); + virtual ~HTMLWin32ObjectOwnerAccessible() {} + + // Accessible + virtual void Shutdown(); + virtual mozilla::a11y::role NativeRole() const override; + virtual bool NativelyUnavailable() const; + + protected: + void* mHwnd; + RefPtr<Accessible> mNativeAccessible; +}; + +/** + * This class is used only internally, we never! send out an IAccessible linked + * back to this object. This class is used to represent a plugin object when + * referenced as a child or sibling of another Accessible node. We need only + * a limited portion of the Accessible interface implemented here. The + * in depth accessible information will be returned by the actual IAccessible + * object returned by us in Accessible::NewAccessible() that gets the + * IAccessible from the windows system from the window handle. + */ +class HTMLWin32ObjectAccessible : public DummyAccessible { + public: + HTMLWin32ObjectAccessible(void* aHwnd, DocAccessible* aDoc); + virtual ~HTMLWin32ObjectAccessible() {} + + virtual void GetNativeInterface(void** aNativeAccessible) override; + + protected: + void* mHwnd; +#if defined(MOZ_SANDBOX) + mscom::ProxyUniquePtr<IAccessible> mCOMProxy; +#endif +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/HyperTextAccessibleWrap.cpp b/accessible/windows/msaa/HyperTextAccessibleWrap.cpp new file mode 100644 index 0000000000..bf1c9ed88e --- /dev/null +++ b/accessible/windows/msaa/HyperTextAccessibleWrap.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "HyperTextAccessibleWrap.h" +#include "Accessible-inl.h" + +#include "nsEventShell.h" + +#include "mozilla/StaticPtr.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessibleWrap, HyperTextAccessible) + +STDMETHODIMP +HyperTextAccessibleWrap::QueryInterface(REFIID aIID, void** aInstancePtr) { + if (!aInstancePtr) return E_FAIL; + + *aInstancePtr = nullptr; + + if (IsTextRole()) { + if (aIID == IID_IAccessibleText) + *aInstancePtr = + static_cast<IAccessibleText*>(static_cast<ia2AccessibleText*>(this)); + else if (aIID == IID_IAccessibleHypertext) + *aInstancePtr = static_cast<IAccessibleHypertext*>(this); + else if (aIID == IID_IAccessibleHypertext2) + *aInstancePtr = static_cast<IAccessibleHypertext2*>(this); + else if (aIID == IID_IAccessibleEditableText) + *aInstancePtr = static_cast<IAccessibleEditableText*>(this); + + if (*aInstancePtr) { + AddRef(); + return S_OK; + } + } + + return AccessibleWrap::QueryInterface(aIID, aInstancePtr); +} + +nsresult HyperTextAccessibleWrap::HandleAccEvent(AccEvent* aEvent) { + uint32_t eventType = aEvent->GetEventType(); + + if (eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED || + eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED) { + Accessible* accessible = aEvent->GetAccessible(); + if (accessible && accessible->IsHyperText()) { + AccTextChangeEvent* event = downcast_accEvent(aEvent); + HyperTextAccessibleWrap* text = + static_cast<HyperTextAccessibleWrap*>(accessible->AsHyperText()); + ia2AccessibleText::UpdateTextChangeData( + text, event->IsTextInserted(), event->ModifiedText(), + event->GetStartOffset(), event->GetLength()); + } + } + + return HyperTextAccessible::HandleAccEvent(aEvent); +} diff --git a/accessible/windows/msaa/HyperTextAccessibleWrap.h b/accessible/windows/msaa/HyperTextAccessibleWrap.h new file mode 100644 index 0000000000..d4297d3a28 --- /dev/null +++ b/accessible/windows/msaa/HyperTextAccessibleWrap.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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_HyperTextAccessibleWrap_h__ +#define mozilla_a11y_HyperTextAccessibleWrap_h__ + +#include "HyperTextAccessible.h" +#include "ia2AccessibleEditableText.h" +#include "ia2AccessibleHypertext.h" +#include "IUnknownImpl.h" + +namespace mozilla { +template <class T> +class StaticAutoPtr; +template <class T> +class StaticRefPtr; + +namespace a11y { + +class HyperTextAccessibleWrap : public HyperTextAccessible, + public ia2AccessibleHypertext, + public ia2AccessibleEditableText { + public: + HyperTextAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : HyperTextAccessible(aContent, aDoc) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual nsresult HandleAccEvent(AccEvent* aEvent); + + protected: + ~HyperTextAccessibleWrap() {} +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/IUnknownImpl.cpp b/accessible/windows/msaa/IUnknownImpl.cpp new file mode 100644 index 0000000000..b7c0ee0f39 --- /dev/null +++ b/accessible/windows/msaa/IUnknownImpl.cpp @@ -0,0 +1,36 @@ +/* -*- 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 "IUnknownImpl.h" + +#include "nsDebug.h" + +namespace mozilla { +namespace a11y { + +HRESULT +GetHRESULT(nsresult aResult) { + switch (aResult) { + case NS_OK: + return S_OK; + + case NS_ERROR_INVALID_ARG: + return E_INVALIDARG; + + case NS_ERROR_OUT_OF_MEMORY: + return E_OUTOFMEMORY; + + case NS_ERROR_NOT_IMPLEMENTED: + return E_NOTIMPL; + + default: + return E_FAIL; + } +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/windows/msaa/IUnknownImpl.h b/accessible/windows/msaa/IUnknownImpl.h new file mode 100644 index 0000000000..eb979e7acd --- /dev/null +++ b/accessible/windows/msaa/IUnknownImpl.h @@ -0,0 +1,161 @@ +/* -*- 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_IUnknownImpl_h_ +#define mozilla_a11y_IUnknownImpl_h_ + +#include <windows.h> +#undef CreateEvent // thank you windows you're such a helper +#include "nsError.h" + +// Avoid warning C4509 like "nonstandard extension used: +// 'AccessibleWrap::[acc_getName]' uses SEH and 'name' has destructor. +// At this point we're catching a crash which is of much greater +// importance than the missing dereference for the nsCOMPtr<> +#ifdef _MSC_VER +# pragma warning(disable : 4509) +#endif + +#if defined(__GNUC__) || defined(__clang__) +# define ATTRIBUTE_UNUSED __attribute__((unused)) +#else +# define ATTRIBUTE_UNUSED +#endif + +namespace mozilla { +namespace a11y { + +class AutoRefCnt { + public: + AutoRefCnt() : mValue(0) {} + + ULONG operator++() { return ++mValue; } + ULONG operator--() { return --mValue; } + ULONG operator++(int) { return ++mValue; } + ULONG operator--(int) { return --mValue; } + + operator ULONG() const { return mValue; } + + private: + ULONG mValue; +}; + +} // namespace a11y +} // namespace mozilla + +#define DECL_IUNKNOWN \ + public: \ + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); \ + ULONG STDMETHODCALLTYPE AddRef() final { \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + ++mRefCnt; \ + return mRefCnt; \ + } \ + ULONG STDMETHODCALLTYPE Release() final { \ + 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**); + +#define IMPL_IUNKNOWN_QUERY_HEAD(Class) \ + STDMETHODIMP \ + Class::QueryInterface(REFIID aIID, void** aInstancePtr) { \ + if (!aInstancePtr) return E_INVALIDARG; \ + *aInstancePtr = nullptr; \ + \ + HRESULT hr ATTRIBUTE_UNUSED = E_NOINTERFACE; + +#define IMPL_IUNKNOWN_QUERY_TAIL \ + return hr; \ + } + +#define IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(Member) \ + return Member->QueryInterface(aIID, aInstancePtr); \ + } + +#define IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(BaseClass) \ + return BaseClass::QueryInterface(aIID, aInstancePtr); \ + } + +#define IMPL_IUNKNOWN_QUERY_IFACE(Iface) \ + if (aIID == IID_##Iface) { \ + *aInstancePtr = static_cast<Iface*>(this); \ + AddRef(); \ + return S_OK; \ + } + +#define IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(Iface, aResolveIface) \ + if (aIID == IID_##Iface) { \ + *aInstancePtr = static_cast<Iface*>(static_cast<aResolveIface*>(this)); \ + AddRef(); \ + return S_OK; \ + } + +#define IMPL_IUNKNOWN_QUERY_CLASS(Class) \ + hr = Class::QueryInterface(aIID, aInstancePtr); \ + if (SUCCEEDED(hr)) return hr; + +#define IMPL_IUNKNOWN_QUERY_CLASS_COND(Class, Cond) \ + if (Cond) { \ + hr = Class::QueryInterface(aIID, aInstancePtr); \ + if (SUCCEEDED(hr)) return hr; \ + } + +#define IMPL_IUNKNOWN_QUERY_AGGR_COND(Member, Cond) \ + if (Cond) { \ + hr = Member->QueryInterface(aIID, aInstancePtr); \ + if (SUCCEEDED(hr)) return hr; \ + } + +#define IMPL_IUNKNOWN1(Class, I1) \ + IMPL_IUNKNOWN_QUERY_HEAD(Class) \ + IMPL_IUNKNOWN_QUERY_IFACE(I1); \ + IMPL_IUNKNOWN_QUERY_IFACE(IUnknown); \ + IMPL_IUNKNOWN_QUERY_TAIL + +#define IMPL_IUNKNOWN2(Class, I1, I2) \ + IMPL_IUNKNOWN_QUERY_HEAD(Class) \ + IMPL_IUNKNOWN_QUERY_IFACE(I1); \ + IMPL_IUNKNOWN_QUERY_IFACE(I2); \ + IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, I1); \ + IMPL_IUNKNOWN_QUERY_TAIL + +#define IMPL_IUNKNOWN_INHERITED1(Class, Super0, Super1) \ + IMPL_IUNKNOWN_QUERY_HEAD(Class) \ + IMPL_IUNKNOWN_QUERY_CLASS(Super1); \ + IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(Super0) + +#define IMPL_IUNKNOWN_INHERITED2(Class, Super0, Super1, Super2) \ + IMPL_IUNKNOWN_QUERY_HEAD(Class) \ + IMPL_IUNKNOWN_QUERY_CLASS(Super1); \ + IMPL_IUNKNOWN_QUERY_CLASS(Super2); \ + IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(Super0) + +namespace mozilla { +namespace a11y { + +/** + * Converts nsresult to HRESULT. + */ +HRESULT GetHRESULT(nsresult aResult); + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/ImageAccessibleWrap.cpp b/accessible/windows/msaa/ImageAccessibleWrap.cpp new file mode 100644 index 0000000000..cdf38f4595 --- /dev/null +++ b/accessible/windows/msaa/ImageAccessibleWrap.cpp @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "ImageAccessibleWrap.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +NS_IMPL_ISUPPORTS_INHERITED0(ImageAccessibleWrap, ImageAccessible) + +IMPL_IUNKNOWN_INHERITED1(ImageAccessibleWrap, AccessibleWrap, + ia2AccessibleImage) diff --git a/accessible/windows/msaa/ImageAccessibleWrap.h b/accessible/windows/msaa/ImageAccessibleWrap.h new file mode 100644 index 0000000000..b6732138ee --- /dev/null +++ b/accessible/windows/msaa/ImageAccessibleWrap.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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_ImageAccessibleWrap_h__ +#define mozilla_a11y_ImageAccessibleWrap_h__ + +#include "ImageAccessible.h" +#include "ia2AccessibleImage.h" + +namespace mozilla { +namespace a11y { + +class ImageAccessibleWrap : public ImageAccessible, public ia2AccessibleImage { + public: + ImageAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : ImageAccessible(aContent, aDoc) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + protected: + ~ImageAccessibleWrap() {} +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/LazyInstantiator.cpp b/accessible/windows/msaa/LazyInstantiator.cpp new file mode 100644 index 0000000000..27ffdf8b48 --- /dev/null +++ b/accessible/windows/msaa/LazyInstantiator.cpp @@ -0,0 +1,733 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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 "LazyInstantiator.h" + +#include "MainThreadUtils.h" +#include "mozilla/a11y/Accessible.h" +#include "mozilla/a11y/AccessibleHandler.h" +#include "mozilla/a11y/Compatibility.h" +#include "mozilla/a11y/Platform.h" +#include "mozilla/Assertions.h" +#include "mozilla/mscom/ProcessRuntime.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsAccessibilityService.h" +#include "nsWindowsHelpers.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsXPCOM.h" +#include "RootAccessibleWrap.h" +#include "WinUtils.h" +#include "prenv.h" + +#include <oaidl.h> + +#if !defined(STATE_SYSTEM_NORMAL) +# define STATE_SYSTEM_NORMAL (0) +#endif // !defined(STATE_SYSTEM_NORMAL) + +#define DLL_BLOCKLIST_ENTRY(name, ...) {L##name, __VA_ARGS__}, +#define DLL_BLOCKLIST_STRING_TYPE const wchar_t* +#include "mozilla/WindowsDllBlocklistA11yDefs.h" + +namespace mozilla { +namespace a11y { + +static const wchar_t kLazyInstantiatorProp[] = + L"mozilla::a11y::LazyInstantiator"; + +/* static */ +already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) { + // There must only be one LazyInstantiator per HWND. + // To track this, we set the kLazyInstantiatorProp on the HWND with a pointer + // to an existing instance. We only create a new LazyInstatiator if that prop + // has not already been set. + LazyInstantiator* existingInstantiator = reinterpret_cast<LazyInstantiator*>( + ::GetProp(aHwnd, kLazyInstantiatorProp)); + + RefPtr<IAccessible> result; + if (existingInstantiator) { + // Temporarily disable blind aggregation until we know that we have been + // marshaled. See EnableBlindAggregation for more information. + existingInstantiator->mAllowBlindAggregation = false; + result = existingInstantiator; + return result.forget(); + } + + // 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()) { + // a11y is not running yet, there are no existing LazyInstantiators for this + // HWND, so create a new one and return it as a surrogate for the root + // accessible. + result = new LazyInstantiator(aHwnd); + return result.forget(); + } + + // a11y is running, so we just resolve the real root accessible. + a11y::Accessible* rootAcc = widget::WinUtils::GetRootAccessibleForHWND(aHwnd); + if (!rootAcc) { + return nullptr; + } + + if (!rootAcc->IsRoot()) { + // rootAcc might represent a popup as opposed to a true root accessible. + // In that case we just use the regular Accessible::GetNativeInterface. + rootAcc->GetNativeInterface(getter_AddRefs(result)); + return result.forget(); + } + + // Subtle: rootAcc might still be wrapped by a LazyInstantiator, but we + // 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. + a11y::RootAccessibleWrap* rootWrap = + static_cast<a11y::RootAccessibleWrap*>(rootAcc); + RefPtr<IUnknown> punk(rootWrap->GetInternalUnknown()); + + MOZ_ASSERT(punk); + if (!punk) { + return nullptr; + } + + punk->QueryInterface(IID_IAccessible, getter_AddRefs(result)); + return result.forget(); +} + +/** + * When marshaling an interface, COM makes a whole bunch of QueryInterface + * calls to determine what kind of marshaling the interface supports. We need + * to handle those queries without instantiating a11y, so we temporarily + * disable passing through of QueryInterface calls to a11y. Once we know that + * COM is finished marshaling, we call EnableBlindAggregation to re-enable + * QueryInterface passthrough. + */ +/* static */ +void LazyInstantiator::EnableBlindAggregation(HWND aHwnd) { + LazyInstantiator* existingInstantiator = reinterpret_cast<LazyInstantiator*>( + ::GetProp(aHwnd, kLazyInstantiatorProp)); + + if (!existingInstantiator) { + return; + } + + existingInstantiator->mAllowBlindAggregation = true; +} + +LazyInstantiator::LazyInstantiator(HWND aHwnd) + : mHwnd(aHwnd), + mAllowBlindAggregation(false), + mWeakRootAccWrap(nullptr), + mWeakAccessible(nullptr), + mWeakDispatch(nullptr) { + MOZ_ASSERT(aHwnd); + // Assign ourselves as the designated LazyInstantiator for aHwnd + DebugOnly<BOOL> setPropOk = + ::SetProp(aHwnd, kLazyInstantiatorProp, reinterpret_cast<HANDLE>(this)); + MOZ_ASSERT(setPropOk); +} + +LazyInstantiator::~LazyInstantiator() { + if (mRealRootUnk) { + // Disconnect ourselves from the root accessible. + RefPtr<IUnknown> dummy(mWeakRootAccWrap->Aggregate(nullptr)); + } + + ClearProp(); +} + +void LazyInstantiator::ClearProp() { + // Remove ourselves as the designated LazyInstantiator for mHwnd + DebugOnly<HANDLE> removedProp = ::RemoveProp(mHwnd, kLazyInstantiatorProp); + MOZ_ASSERT(!removedProp || + reinterpret_cast<LazyInstantiator*>(removedProp.value) == this); +} + +/** + * Given the remote client's thread ID, resolve its process ID. + */ +DWORD +LazyInstantiator::GetClientPid(const DWORD aClientTid) { + nsAutoHandle callingThread( + ::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, aClientTid)); + if (!callingThread) { + return 0; + } + + return ::GetProcessIdOfThread(callingThread); +} + +/** + * This is the blocklist for known "bad" remote clients that instantiate a11y. + */ +static const char* gBlockedRemoteClients[] = { + "tbnotifier.exe", // Ask.com Toolbar, bug 1453876 + "flow.exe" // Conexant Flow causes performance issues, bug 1569712 +}; + +/** + * Check for the presence of any known "bad" injected DLLs that may be trying + * to instantiate a11y. + * + * @return true to block a11y instantiation, otherwise false to continue + */ +bool LazyInstantiator::IsBlockedInjection() { + // Check debugging options see if we should disable the blocklist. + if (PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) { + return false; + } + + if (Compatibility::HasKnownNonUiaConsumer()) { + // If we already see a known AT, don't block a11y instantiation + return false; + } + + for (size_t index = 0, len = ArrayLength(gBlockedInprocDlls); index < len; + ++index) { + const DllBlockInfo& blockedDll = gBlockedInprocDlls[index]; + HMODULE module = ::GetModuleHandleW(blockedDll.mName); + if (!module) { + // This dll isn't loaded. + continue; + } + + LauncherResult<ModuleVersion> version = GetModuleVersion(module); + return version.isOk() && blockedDll.IsVersionBlocked(version.unwrap()); + } + + return false; +} + +/** + * Given a remote client's thread ID, determine whether we should proceed with + * a11y instantiation. This is where telemetry should be gathered and any + * potential blocking of unwanted a11y clients should occur. + * + * @return true if we should instantiate a11y + */ +bool LazyInstantiator::ShouldInstantiate(const DWORD aClientTid) { + if (!aClientTid) { + // aClientTid == 0 implies that this is either an in-process call, or else + // we failed to retrieve information about the remote caller. + // We should always default to instantiating a11y in this case, provided + // that we don't see any known bad injected DLLs. + return !IsBlockedInjection(); + } + + a11y::SetInstantiator(GetClientPid(aClientTid)); + + nsCOMPtr<nsIFile> clientExe; + if (!a11y::GetInstantiator(getter_AddRefs(clientExe))) { + return true; + } + + nsresult rv; + if (!PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) { + // Debugging option is not present, so check blocklist. + nsAutoString leafName; + rv = clientExe->GetLeafName(leafName); + if (NS_SUCCEEDED(rv)) { + for (size_t i = 0, len = ArrayLength(gBlockedRemoteClients); i < len; + ++i) { + if (leafName.EqualsIgnoreCase(gBlockedRemoteClients[i])) { + // If client exe is in our blocklist, do not instantiate. + return false; + } + } + } + } + + return true; +} + +RootAccessibleWrap* LazyInstantiator::ResolveRootAccWrap() { + Accessible* acc = widget::WinUtils::GetRootAccessibleForHWND(mHwnd); + if (!acc || !acc->IsRoot()) { + return nullptr; + } + + return static_cast<RootAccessibleWrap*>(acc); +} + +/** + * With COM aggregation, the aggregated inner object usually delegates its + * reference counting to the outer object. In other words, we would expect + * mRealRootUnk to delegate its AddRef() and Release() to this LazyInstantiator. + * + * This scheme will not work in our case because the RootAccessibleWrap is + * cycle-collected! + * + * Instead, once a LazyInstantiator aggregates a RootAccessibleWrap, we transfer + * our strong references into mRealRootUnk. Any future calls to AddRef or + * Release now operate on mRealRootUnk instead of our intrinsic reference + * count. This is a bit strange, but it is the only way for these objects to + * share their reference count in a way that is safe for cycle collection. + * + * How do we know when it is safe to destroy ourselves? In + * LazyInstantiator::Release, we examine the result of mRealRootUnk->Release(). + * If mRealRootUnk's resulting refcount is 1, then we know that the only + * remaining reference to mRealRootUnk is the mRealRootUnk reference itself (and + * thus nobody else holds references to either this or mRealRootUnk). Therefore + * we may now delete ourselves. + */ +void LazyInstantiator::TransplantRefCnt() { + MOZ_ASSERT(mRefCnt > 0); + MOZ_ASSERT(mRealRootUnk); + + while (mRefCnt > 0) { + mRealRootUnk.get()->AddRef(); + --mRefCnt; + } +} + +HRESULT +LazyInstantiator::MaybeResolveRoot() { + MOZ_ASSERT(NS_IsMainThread()); + if (mWeakAccessible) { + return S_OK; + } + + if (GetAccService() || + ShouldInstantiate(mscom::ProcessRuntime::GetClientThreadId())) { + mWeakRootAccWrap = ResolveRootAccWrap(); + if (!mWeakRootAccWrap) { + return E_POINTER; + } + + // Wrap ourselves around the root accessible wrap + mRealRootUnk = mWeakRootAccWrap->Aggregate(static_cast<IAccessible*>(this)); + if (!mRealRootUnk) { + return E_FAIL; + } + + // Move our strong references into the root accessible (see the comments + // above TransplantRefCnt for explanation). + TransplantRefCnt(); + + // Now obtain mWeakAccessible which we use to forward our incoming calls + // to the real accesssible. + HRESULT hr = + mRealRootUnk->QueryInterface(IID_IAccessible, (void**)&mWeakAccessible); + if (FAILED(hr)) { + return hr; + } + + // mWeakAccessible is weak, so don't hold a strong ref + mWeakAccessible->Release(); + + // Now that a11y is running, we don't need to remain registered with our + // HWND anymore. + ClearProp(); + + return S_OK; + } + + // If we don't want a real root, let's resolve a fake one. + + const WPARAM flags = 0xFFFFFFFFUL; + // Synthesize a WM_GETOBJECT request to obtain a system-implemented + // IAccessible object from DefWindowProc + LRESULT lresult = ::DefWindowProc(mHwnd, WM_GETOBJECT, flags, + static_cast<LPARAM>(OBJID_CLIENT)); + + HRESULT hr = ObjectFromLresult(lresult, IID_IAccessible, flags, + getter_AddRefs(mRealRootUnk)); + if (FAILED(hr)) { + return hr; + } + + if (!mRealRootUnk) { + return E_NOTIMPL; + } + + hr = mRealRootUnk->QueryInterface(IID_IAccessible, (void**)&mWeakAccessible); + if (FAILED(hr)) { + return hr; + } + + // mWeakAccessible is weak, so don't hold a strong ref + mWeakAccessible->Release(); + + return S_OK; +} + +#define RESOLVE_ROOT \ + { \ + HRESULT hr = MaybeResolveRoot(); \ + if (FAILED(hr)) { \ + return hr; \ + } \ + } + +IMPL_IUNKNOWN_QUERY_HEAD(LazyInstantiator) +IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, IAccessible) +IMPL_IUNKNOWN_QUERY_IFACE(IAccessible) +IMPL_IUNKNOWN_QUERY_IFACE(IDispatch) +IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider) +// See EnableBlindAggregation for comments. +if (!mAllowBlindAggregation) { + return E_NOINTERFACE; +} + +if (aIID == IID_IAccIdentity) { + return E_NOINTERFACE; +} +// If the client queries for an interface that LazyInstantiator does not +// intrinsically support, then we must resolve the root accessible and pass +// on the QueryInterface call to mRealRootUnk. +RESOLVE_ROOT +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mRealRootUnk) + +ULONG +LazyInstantiator::AddRef() { + // Always delegate refcounting to mRealRootUnk when it exists + if (mRealRootUnk) { + return mRealRootUnk.get()->AddRef(); + } + + return ++mRefCnt; +} + +ULONG +LazyInstantiator::Release() { + ULONG result; + + // Always delegate refcounting to mRealRootUnk when it exists + if (mRealRootUnk) { + result = mRealRootUnk.get()->Release(); + if (result == 1) { + // mRealRootUnk is the only strong reference left, so nothing else holds + // a strong reference to us. Drop result to zero so that we destroy + // ourselves (See the comments above LazyInstantiator::TransplantRefCnt + // for more info). + --result; + } + } else { + result = --mRefCnt; + } + + if (!result) { + delete this; + } + return result; +} + +/** + * Create a standard IDispatch implementation. mStdDispatch will translate any + * IDispatch::Invoke calls into real IAccessible calls. + */ +HRESULT +LazyInstantiator::ResolveDispatch() { + if (mWeakDispatch) { + return S_OK; + } + + // The IAccessible typelib is embedded in oleacc.dll's resources. + auto typelib = mscom::RegisterTypelib( + L"oleacc.dll", mscom::RegistrationFlags::eUseSystemDirectory); + if (!typelib) { + return E_UNEXPECTED; + } + + // Extract IAccessible's type info + RefPtr<ITypeInfo> accTypeInfo; + HRESULT hr = + typelib->GetTypeInfoForGuid(IID_IAccessible, getter_AddRefs(accTypeInfo)); + if (FAILED(hr)) { + return hr; + } + + // Now create the standard IDispatch for IAccessible + hr = ::CreateStdDispatch(static_cast<IAccessible*>(this), + static_cast<IAccessible*>(this), accTypeInfo, + getter_AddRefs(mStdDispatch)); + if (FAILED(hr)) { + return hr; + } + + hr = mStdDispatch->QueryInterface(IID_IDispatch, (void**)&mWeakDispatch); + if (FAILED(hr)) { + return hr; + } + + // WEAK reference + mWeakDispatch->Release(); + return S_OK; +} + +#define RESOLVE_IDISPATCH \ + { \ + HRESULT hr = ResolveDispatch(); \ + if (FAILED(hr)) { \ + return hr; \ + } \ + } + +/** + * The remaining methods implement IDispatch, IAccessible, and IServiceProvider, + * lazily resolving the real a11y objects and passing the call through. + */ + +HRESULT +LazyInstantiator::GetTypeInfoCount(UINT* pctinfo) { + RESOLVE_IDISPATCH; + return mWeakDispatch->GetTypeInfoCount(pctinfo); +} + +HRESULT +LazyInstantiator::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { + RESOLVE_IDISPATCH; + return mWeakDispatch->GetTypeInfo(iTInfo, lcid, ppTInfo); +} + +HRESULT +LazyInstantiator::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) { + RESOLVE_IDISPATCH; + return mWeakDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); +} + +HRESULT +LazyInstantiator::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS* pDispParams, + VARIANT* pVarResult, EXCEPINFO* pExcepInfo, + UINT* puArgErr) { + RESOLVE_IDISPATCH; + return mWeakDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, + pVarResult, pExcepInfo, puArgErr); +} + +HRESULT +LazyInstantiator::get_accParent(IDispatch** ppdispParent) { + if (!mWeakAccessible) { + // If we'd resolve the root right now this would be the codepath we'd end + // up in anyway. So we might as well return it here. + return ::AccessibleObjectFromWindow(mHwnd, OBJID_WINDOW, IID_IAccessible, + (void**)ppdispParent); + } + RESOLVE_ROOT; + return mWeakAccessible->get_accParent(ppdispParent); +} + +HRESULT +LazyInstantiator::get_accChildCount(long* pcountChildren) { + if (!pcountChildren) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accChildCount(pcountChildren); +} + +HRESULT +LazyInstantiator::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { + if (!ppdispChild) { + return E_INVALIDARG; + } + + if (V_VT(&varChild) == VT_I4 && V_I4(&varChild) == CHILDID_SELF) { + RefPtr<IDispatch> disp(this); + disp.forget(ppdispChild); + return S_OK; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accChild(varChild, ppdispChild); +} + +HRESULT +LazyInstantiator::get_accName(VARIANT varChild, BSTR* pszName) { + if (!pszName) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accName(varChild, pszName); +} + +HRESULT +LazyInstantiator::get_accValue(VARIANT varChild, BSTR* pszValue) { + if (!pszValue) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accValue(varChild, pszValue); +} + +HRESULT +LazyInstantiator::get_accDescription(VARIANT varChild, BSTR* pszDescription) { + if (!pszDescription) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accDescription(varChild, pszDescription); +} + +HRESULT +LazyInstantiator::get_accRole(VARIANT varChild, VARIANT* pvarRole) { + if (!pvarRole) { + return E_INVALIDARG; + } + + if (V_VT(&varChild) == VT_I4 && V_I4(&varChild) == CHILDID_SELF) { + V_VT(pvarRole) = VT_I4; + V_I4(pvarRole) = ROLE_SYSTEM_APPLICATION; + return S_OK; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accRole(varChild, pvarRole); +} + +HRESULT +LazyInstantiator::get_accState(VARIANT varChild, VARIANT* pvarState) { + if (!pvarState) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accState(varChild, pvarState); +} + +HRESULT +LazyInstantiator::get_accHelp(VARIANT varChild, BSTR* pszHelp) { + return E_NOTIMPL; +} + +HRESULT +LazyInstantiator::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) { + return E_NOTIMPL; +} + +HRESULT +LazyInstantiator::get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) { + if (!pszKeyboardShortcut) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accKeyboardShortcut(varChild, + pszKeyboardShortcut); +} + +HRESULT +LazyInstantiator::get_accFocus(VARIANT* pvarChild) { + if (!pvarChild) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accFocus(pvarChild); +} + +HRESULT +LazyInstantiator::get_accSelection(VARIANT* pvarChildren) { + if (!pvarChildren) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accSelection(pvarChildren); +} + +HRESULT +LazyInstantiator::get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) { + if (!pszDefaultAction) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->get_accDefaultAction(varChild, pszDefaultAction); +} + +HRESULT +LazyInstantiator::accSelect(long flagsSelect, VARIANT varChild) { + RESOLVE_ROOT; + return mWeakAccessible->accSelect(flagsSelect, varChild); +} + +HRESULT +LazyInstantiator::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) { + RESOLVE_ROOT; + return mWeakAccessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, + varChild); +} + +HRESULT +LazyInstantiator::accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) { + if (!pvarEndUpAt) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->accNavigate(navDir, varStart, pvarEndUpAt); +} + +HRESULT +LazyInstantiator::accHitTest(long xLeft, long yTop, VARIANT* pvarChild) { + if (!pvarChild) { + return E_INVALIDARG; + } + + RESOLVE_ROOT; + return mWeakAccessible->accHitTest(xLeft, yTop, pvarChild); +} + +HRESULT +LazyInstantiator::accDoDefaultAction(VARIANT varChild) { + RESOLVE_ROOT; + return mWeakAccessible->accDoDefaultAction(varChild); +} + +HRESULT +LazyInstantiator::put_accName(VARIANT varChild, BSTR szName) { + return E_NOTIMPL; +} + +HRESULT +LazyInstantiator::put_accValue(VARIANT varChild, BSTR szValue) { + return E_NOTIMPL; +} + +HRESULT +LazyInstantiator::QueryService(REFGUID aServiceId, REFIID aServiceIid, + void** aOutInterface) { + if (!aOutInterface) { + return E_INVALIDARG; + } + + for (const GUID& unsupportedService : kUnsupportedServices) { + if (aServiceId == unsupportedService) { + return E_NOINTERFACE; + } + } + + *aOutInterface = nullptr; + + RESOLVE_ROOT; + + RefPtr<IServiceProvider> servProv; + HRESULT hr = mRealRootUnk->QueryInterface(IID_IServiceProvider, + getter_AddRefs(servProv)); + if (FAILED(hr)) { + return hr; + } + + return servProv->QueryService(aServiceId, aServiceIid, aOutInterface); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/windows/msaa/LazyInstantiator.h b/accessible/windows/msaa/LazyInstantiator.h new file mode 100644 index 0000000000..e5cd61f273 --- /dev/null +++ b/accessible/windows/msaa/LazyInstantiator.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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_LazyInstantiator_h +#define mozilla_a11y_LazyInstantiator_h + +#include "IUnknownImpl.h" +#include "mozilla/mscom/Ptr.h" +#include "mozilla/RefPtr.h" +#include "nsString.h" + +#include <oleacc.h> + +class nsIFile; + +namespace mozilla { +namespace a11y { + +class RootAccessibleWrap; + +/** + * LazyInstantiator is an IAccessible that initially acts as a placeholder. + * The a11y service is not actually started until two conditions are met: + * + * (1) A method is called on the LazyInstantiator that would require a11y + * services in order to fulfill; and + * (2) LazyInstantiator::ShouldInstantiate returns true. + */ +class LazyInstantiator final : public IAccessible, public IServiceProvider { + public: + [[nodiscard]] static already_AddRefed<IAccessible> GetRootAccessible( + HWND aHwnd); + static void EnableBlindAggregation(HWND aHwnd); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IDispatch + STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) override; + STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo** ppTInfo) override; + STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) override; + STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, UINT* puArgErr) override; + + // IAccessible + STDMETHODIMP get_accParent(IDispatch** ppdispParent) override; + STDMETHODIMP get_accChildCount(long* pcountChildren) override; + STDMETHODIMP get_accChild(VARIANT varChild, IDispatch** ppdispChild) override; + STDMETHODIMP get_accName(VARIANT varChild, BSTR* pszName) override; + STDMETHODIMP get_accValue(VARIANT varChild, BSTR* pszValue) override; + STDMETHODIMP get_accDescription(VARIANT varChild, + BSTR* pszDescription) override; + STDMETHODIMP get_accRole(VARIANT varChild, VARIANT* pvarRole) override; + STDMETHODIMP get_accState(VARIANT varChild, VARIANT* pvarState) override; + STDMETHODIMP get_accHelp(VARIANT varChild, BSTR* pszHelp) override; + STDMETHODIMP get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) override; + STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) override; + STDMETHODIMP get_accFocus(VARIANT* pvarChild) override; + STDMETHODIMP get_accSelection(VARIANT* pvarChildren) override; + STDMETHODIMP get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) override; + STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild) override; + STDMETHODIMP accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) override; + STDMETHODIMP accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) override; + STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT* pvarChild) override; + STDMETHODIMP accDoDefaultAction(VARIANT varChild) override; + STDMETHODIMP put_accName(VARIANT varChild, BSTR szName) override; + STDMETHODIMP put_accValue(VARIANT varChild, BSTR szValue) override; + + // IServiceProvider + STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aServiceIid, + void** aOutInterface) override; + + private: + explicit LazyInstantiator(HWND aHwnd); + ~LazyInstantiator(); + + bool IsBlockedInjection(); + bool ShouldInstantiate(const DWORD aClientTid); + + DWORD GetClientPid(const DWORD aClientTid); + + /** + * @return S_OK if we have a valid mRealRoot to invoke methods on + */ + HRESULT MaybeResolveRoot(); + + /** + * @return S_OK if we have a valid mWeakDispatch to invoke methods on + */ + HRESULT ResolveDispatch(); + + RootAccessibleWrap* ResolveRootAccWrap(); + void TransplantRefCnt(); + void ClearProp(); + + private: + mozilla::a11y::AutoRefCnt mRefCnt; + HWND mHwnd; + bool mAllowBlindAggregation; + RefPtr<IUnknown> mRealRootUnk; + RefPtr<IUnknown> mStdDispatch; + /** + * mWeakRootAccWrap, mWeakAccessible and mWeakDispatch are weak because they + * are interfaces that come from objects that we aggregate. Aggregated object + * interfaces share refcount methods with ours, so if we were to hold strong + * references to them, we would be holding strong references to ourselves, + * creating a cycle. + */ + RootAccessibleWrap* mWeakRootAccWrap; + IAccessible* mWeakAccessible; + IDispatch* mWeakDispatch; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_LazyInstantiator_h diff --git a/accessible/windows/msaa/MsaaIdGenerator.cpp b/accessible/windows/msaa/MsaaIdGenerator.cpp new file mode 100644 index 0000000000..f69a43381c --- /dev/null +++ b/accessible/windows/msaa/MsaaIdGenerator.cpp @@ -0,0 +1,229 @@ +/* -*- 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 "MsaaIdGenerator.h" + +#include "mozilla/a11y/AccessibleWrap.h" +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "nsDataHashtable.h" +#include "sdnAccessible.h" + +// These constants may be adjusted to modify the proportion of the Child ID +// allocated to the content ID vs proportion allocated to the unique ID. They +// must always sum to 31, ie. the width of a 32-bit integer less the sign bit. + +// NB: kNumContentProcessIDBits must be large enough to successfully hold the +// maximum permitted number of e10s content processes. If the e10s maximum +// number of content processes changes, then kNumContentProcessIDBits must also +// be updated if necessary to accommodate that new value! +static const uint32_t kNumContentProcessIDBits = 7UL; +static const uint32_t kNumUniqueIDBits = (31UL - kNumContentProcessIDBits); + +static_assert( + kNumContentProcessIDBits + kNumUniqueIDBits == 31, + "Allocation of Content ID bits and Unique ID bits must sum to 31"); + +namespace mozilla { +namespace a11y { +namespace detail { + +typedef nsDataHashtable<nsUint64HashKey, uint32_t> ContentParentIdMap; + +#pragma pack(push, 1) +union MsaaID { + int32_t mInt32; + uint32_t mUInt32; + struct { + uint32_t mUniqueID : kNumUniqueIDBits; + uint32_t mContentProcessID : kNumContentProcessIDBits; + uint32_t mSignBit : 1; + } mCracked; +}; +#pragma pack(pop) + +static uint32_t BuildMsaaID(const uint32_t aID, + const uint32_t aContentProcessID) { + MsaaID id; + id.mCracked.mSignBit = 0; + id.mCracked.mUniqueID = aID; + id.mCracked.mContentProcessID = aContentProcessID; + return ~id.mUInt32; +} + +class MsaaIDCracker { + public: + explicit MsaaIDCracker(const uint32_t aMsaaID) { mID.mUInt32 = ~aMsaaID; } + + uint32_t GetContentProcessId() { return mID.mCracked.mContentProcessID; } + + uint32_t GetUniqueId() { return mID.mCracked.mUniqueID; } + + private: + MsaaID mID; +}; + +} // namespace detail + +constexpr MsaaIdGenerator::MsaaIdGenerator() : mIDSet(kNumUniqueIDBits) {} + +uint32_t MsaaIdGenerator::GetID() { + uint32_t id = mIDSet.GetID(); + MOZ_ASSERT(id <= ((1UL << kNumUniqueIDBits) - 1UL)); + return detail::BuildMsaaID(id, ResolveContentProcessID()); +} + +bool MsaaIdGenerator::ReleaseID(uint32_t aID) { + MOZ_ASSERT(aID != AccessibleWrap::kNoID); + detail::MsaaIDCracker cracked(aID); + if (cracked.GetContentProcessId() != ResolveContentProcessID()) { + return false; + } + mIDSet.ReleaseID(cracked.GetUniqueId()); + return true; +} + +void MsaaIdGenerator::ReleaseID(NotNull<AccessibleWrap*> aAccWrap) { + if (!ReleaseID(aAccWrap->GetExistingID())) { + // This may happen if chrome holds a proxy whose ID was originally generated + // by a content process. Since ReleaseID only has meaning in the process + // that originally generated that ID, we ignore ReleaseID calls for any ID + // that did not come from the current process. + MOZ_ASSERT(aAccWrap->IsProxy()); + } +} + +void MsaaIdGenerator::ReleaseID(NotNull<sdnAccessible*> aSdnAcc) { + Maybe<uint32_t> id = aSdnAcc->ReleaseUniqueID(); + if (id.isSome()) { + DebugOnly<bool> released = ReleaseID(id.value()); + MOZ_ASSERT(released); + } +} + +bool MsaaIdGenerator::IsChromeID(uint32_t aID) { + detail::MsaaIDCracker cracked(aID); + return cracked.GetContentProcessId() == 0; +} + +bool MsaaIdGenerator::IsIDForThisContentProcess(uint32_t aID) { + MOZ_ASSERT(XRE_IsContentProcess()); + detail::MsaaIDCracker cracked(aID); + return cracked.GetContentProcessId() == ResolveContentProcessID(); +} + +bool MsaaIdGenerator::IsIDForContentProcess( + uint32_t aID, dom::ContentParentId aIPCContentProcessId) { + MOZ_ASSERT(XRE_IsParentProcess()); + detail::MsaaIDCracker cracked(aID); + return cracked.GetContentProcessId() == + GetContentProcessIDFor(aIPCContentProcessId); +} + +bool MsaaIdGenerator::IsSameContentProcessFor(uint32_t aFirstID, + uint32_t aSecondID) { + detail::MsaaIDCracker firstCracked(aFirstID); + detail::MsaaIDCracker secondCracked(aSecondID); + return firstCracked.GetContentProcessId() == + secondCracked.GetContentProcessId(); +} + +uint32_t MsaaIdGenerator::ResolveContentProcessID() { + if (XRE_IsParentProcess()) { + return 0; + } + + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + uint32_t result = contentChild->GetMsaaID(); + + MOZ_ASSERT(result); + return result; +} + +/** + * Each dom::ContentParent has a 64-bit ID. This ID is monotonically increasing + * with each new content process, so those IDs are effectively single-use. OTOH, + * MSAA requires 32-bit IDs. Since we only allocate kNumContentProcessIDBits for + * the content process ID component, the MSAA content process ID value must be + * reusable. sContentParentIdMap holds the current associations between + * dom::ContentParent IDs and the MSAA content parent IDs that have been + * allocated to them. + */ +static StaticAutoPtr<detail::ContentParentIdMap> sContentParentIdMap; + +static const uint32_t kBitsPerByte = 8UL; +// Set sContentProcessIdBitmap[0] to 1 to reserve the Chrome process's id +static uint64_t sContentProcessIdBitmap[(1UL << kNumContentProcessIDBits) / + (sizeof(uint64_t) * kBitsPerByte)] = { + 1ULL}; +static const uint32_t kBitsPerElement = + sizeof(sContentProcessIdBitmap[0]) * kBitsPerByte; + +uint32_t MsaaIdGenerator::GetContentProcessIDFor( + dom::ContentParentId aIPCContentProcessID) { + MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread()); + if (!sContentParentIdMap) { + sContentParentIdMap = new detail::ContentParentIdMap(); + ClearOnShutdown(&sContentParentIdMap); + } + + uint32_t value = 0; + if (sContentParentIdMap->Get(aIPCContentProcessID, &value)) { + return value; + } + + uint32_t index = 0; + for (; index < ArrayLength(sContentProcessIdBitmap); ++index) { + if (sContentProcessIdBitmap[index] == UINT64_MAX) { + continue; + } + uint32_t bitIndex = CountTrailingZeroes64(~sContentProcessIdBitmap[index]); + MOZ_ASSERT(!(sContentProcessIdBitmap[index] & (1ULL << bitIndex))); + MOZ_ASSERT(bitIndex != 0 || index != 0); + sContentProcessIdBitmap[index] |= (1ULL << bitIndex); + value = index * kBitsPerElement + bitIndex; + break; + } + + // If we run out of content process IDs, we're in trouble + MOZ_RELEASE_ASSERT(index < ArrayLength(sContentProcessIdBitmap)); + + sContentParentIdMap->Put(aIPCContentProcessID, value); + return value; +} + +void MsaaIdGenerator::ReleaseContentProcessIDFor( + dom::ContentParentId aIPCContentProcessID) { + MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread()); + if (!sContentParentIdMap) { + // Since Content IDs are generated lazily, ContentParent might attempt + // to release an ID that was never allocated to begin with. + return; + } + + Maybe<uint32_t> mapping = + sContentParentIdMap->GetAndRemove(aIPCContentProcessID); + if (!mapping) { + // Since Content IDs are generated lazily, ContentParent might attempt + // to release an ID that was never allocated to begin with. + return; + } + + uint32_t index = mapping.ref() / kBitsPerElement; + MOZ_ASSERT(index < ArrayLength(sContentProcessIdBitmap)); + + uint64_t mask = 1ULL << (mapping.ref() % kBitsPerElement); + MOZ_ASSERT(sContentProcessIdBitmap[index] & mask); + + sContentProcessIdBitmap[index] &= ~mask; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/windows/msaa/MsaaIdGenerator.h b/accessible/windows/msaa/MsaaIdGenerator.h new file mode 100644 index 0000000000..c449a88d51 --- /dev/null +++ b/accessible/windows/msaa/MsaaIdGenerator.h @@ -0,0 +1,59 @@ +/* -*- 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_MsaaIdGenerator_h +#define mozilla_a11y_MsaaIdGenerator_h + +#include "mozilla/a11y/IDSet.h" + +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/NotNull.h" + +namespace mozilla { +namespace a11y { + +class AccessibleWrap; +class sdnAccessible; + +/** + * This class is responsible for generating child IDs used by our MSAA + * implementation. Since e10s requires us to differentiate IDs based on the + * originating process of the accessible, a portion of the ID's bits are + * allocated to storing that information. The remaining bits represent the + * unique ID of the accessible, within that content process. + * + * The constants kNumContentProcessIDBits and kNumUniqueIDBits in the + * implementation are responsible for determining the proportion of bits that + * are allocated for each purpose. + */ +class MsaaIdGenerator { + public: + constexpr MsaaIdGenerator(); + + uint32_t GetID(); + void ReleaseID(NotNull<AccessibleWrap*> aAccWrap); + void ReleaseID(NotNull<sdnAccessible*> aSdnAcc); + bool IsChromeID(uint32_t aID); + bool IsIDForThisContentProcess(uint32_t aID); + bool IsIDForContentProcess(uint32_t aID, + dom::ContentParentId aIPCContentProcessId); + bool IsSameContentProcessFor(uint32_t aFirstID, uint32_t aSecondID); + + uint32_t GetContentProcessIDFor(dom::ContentParentId aIPCContentProcessID); + void ReleaseContentProcessIDFor(dom::ContentParentId aIPCContentProcessID); + + private: + bool ReleaseID(uint32_t aID); + uint32_t ResolveContentProcessID(); + + private: + IDSet mIDSet; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_MsaaIdGenerator_h diff --git a/accessible/windows/msaa/NtUndoc.h b/accessible/windows/msaa/NtUndoc.h new file mode 100644 index 0000000000..9b9e2341bd --- /dev/null +++ b/accessible/windows/msaa/NtUndoc.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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_NtUndoc_h +#define mozilla_NtUndoc_h + +#include <winternl.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef STATUS_INFO_LENGTH_MISMATCH +# define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#endif + +#ifndef STATUS_BUFFER_TOO_SMALL +# define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#endif + +#ifndef STATUS_MORE_ENTRIES +# define STATUS_MORE_ENTRIES ((NTSTATUS)0x00000105L) +#endif + +enum UndocSystemInformationClass { SystemExtendedHandleInformation = 64 }; + +struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { + PVOID mObject; + ULONG_PTR mPid; + ULONG_PTR mHandle; + ACCESS_MASK mGrantedAccess; + USHORT mCreatorBackTraceIndex; + USHORT mObjectTypeIndex; + ULONG mAttributes; + ULONG mReserved; +}; + +struct SYSTEM_HANDLE_INFORMATION_EX { + ULONG_PTR mHandleCount; + ULONG_PTR mReserved; + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX mHandles[1]; +}; + +#ifndef __MINGW32__ +enum UndocObjectInformationClass { ObjectNameInformation = 1 }; + +struct OBJECT_NAME_INFORMATION { + UNICODE_STRING mName; +}; +#endif + +// The following declarations are documented on MSDN but are not included in +// public user-mode headers. + +enum DirectoryObjectAccessFlags { + DIRECTORY_QUERY = 0x0001, + DIRECTORY_TRAVERSE = 0x0002, + DIRECTORY_CREATE_OBJECT = 0x0004, + DIRECTORY_CREATE_SUBDIRECTORY = 0x0008, + DIRECTORY_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | 0x000F +}; + +NTSTATUS WINAPI NtOpenDirectoryObject(PHANDLE aDirectoryHandle, + ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes); + +struct OBJECT_DIRECTORY_INFORMATION { + UNICODE_STRING mName; + UNICODE_STRING mTypeName; +}; + +NTSTATUS WINAPI NtQueryDirectoryObject(HANDLE aDirectoryHandle, + PVOID aOutBuffer, ULONG aBufferLength, + BOOLEAN aReturnSingleEntry, + BOOLEAN aRestartScan, PULONG aContext, + PULONG aOutReturnLength); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // mozilla_NtUndoc_h diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp new file mode 100644 index 0000000000..196358a04f --- /dev/null +++ b/accessible/windows/msaa/Platform.cpp @@ -0,0 +1,378 @@ +/* -*- 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 "Platform.h" + +#include "AccEvent.h" +#include "Compatibility.h" +#include "HyperTextAccessibleWrap.h" +#include "nsIWindowsRegKey.h" +#include "nsWinUtils.h" +#include "mozilla/a11y/ProxyAccessible.h" +#include "mozilla/mscom/ActivationContext.h" +#include "mozilla/mscom/InterceptorLog.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "ProxyWrappers.h" + +#if defined(MOZ_TELEMETRY_REPORTING) +# include "mozilla/Telemetry.h" +#endif // defined(MOZ_TELEMETRY_REPORTING) + +using namespace mozilla; +using namespace mozilla::a11y; +using namespace mozilla::mscom; + +static StaticAutoPtr<RegisteredProxy> gRegCustomProxy; +static StaticAutoPtr<RegisteredProxy> gRegProxy; +static StaticAutoPtr<RegisteredProxy> gRegAccTlb; +static StaticAutoPtr<RegisteredProxy> gRegMiscTlb; +static StaticRefPtr<nsIFile> gInstantiator; + +void a11y::PlatformInit() { + nsWinUtils::MaybeStartWindowEmulation(); + ia2AccessibleText::InitTextChangeData(); + + mscom::InterceptorLog::Init(); + UniquePtr<RegisteredProxy> regCustomProxy(mscom::RegisterProxy()); + gRegCustomProxy = regCustomProxy.release(); + UniquePtr<RegisteredProxy> regProxy(mscom::RegisterProxy(L"ia2marshal.dll")); + gRegProxy = regProxy.release(); + UniquePtr<RegisteredProxy> regAccTlb(mscom::RegisterTypelib( + L"oleacc.dll", RegistrationFlags::eUseSystemDirectory)); + gRegAccTlb = regAccTlb.release(); + UniquePtr<RegisteredProxy> regMiscTlb( + mscom::RegisterTypelib(L"Accessible.tlb")); + gRegMiscTlb = regMiscTlb.release(); +} + +void a11y::PlatformShutdown() { + ::DestroyCaret(); + + nsWinUtils::ShutdownWindowEmulation(); + gRegCustomProxy = nullptr; + gRegProxy = nullptr; + gRegAccTlb = nullptr; + gRegMiscTlb = nullptr; + + if (gInstantiator) { + gInstantiator = nullptr; + } +} + +void a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces) { + AccessibleWrap* wrapper = nullptr; + if (aInterfaces & Interfaces::DOCUMENT) { + wrapper = new DocProxyAccessibleWrap(aProxy); + } else if (aInterfaces & Interfaces::HYPERTEXT) { + wrapper = new HyperTextProxyAccessibleWrap(aProxy); + } else { + wrapper = new ProxyAccessibleWrap(aProxy); + } + + wrapper->SetProxyInterfaces(aInterfaces); + wrapper->AddRef(); + aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper)); +} + +void a11y::ProxyDestroyed(ProxyAccessible* aProxy) { + AccessibleWrap* wrapper = + reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper()); + + // If aProxy is a document that was created, but + // RecvPDocAccessibleConstructor failed then aProxy->GetWrapper() will be + // null. + if (!wrapper) return; + + if (aProxy->IsDoc() && nsWinUtils::IsWindowEmulationStarted()) { + aProxy->AsDoc()->SetEmulatedWindowHandle(nullptr); + } + + wrapper->Shutdown(); + aProxy->SetWrapper(0); + wrapper->Release(); +} + +void a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType) { + AccessibleWrap::FireWinEvent(WrapperFor(aTarget), aEventType); +} + +void a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t, bool) { + AccessibleWrap::FireWinEvent(WrapperFor(aTarget), + nsIAccessibleEvent::EVENT_STATE_CHANGE); +} + +void a11y::ProxyFocusEvent(ProxyAccessible* aTarget, + const LayoutDeviceIntRect& aCaretRect) { + FocusManager* focusMgr = FocusMgr(); + if (focusMgr && focusMgr->FocusedAccessible()) { + // This is a focus event from a remote document, but focus has moved out + // of that document into the chrome since that event was sent. For example, + // this can happen when choosing File menu -> New Tab. See bug 1471466. + // Note that this does not handle the case where a focus event is sent from + // one remote document, but focus moved into a second remote document + // since that event was sent. However, this isn't something anyone has been + // able to trigger. + return; + } + + AccessibleWrap::UpdateSystemCaretFor(aTarget, aCaretRect); + AccessibleWrap::FireWinEvent(WrapperFor(aTarget), + nsIAccessibleEvent::EVENT_FOCUS); +} + +void a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, + const LayoutDeviceIntRect& aCaretRect) { + AccessibleWrap::UpdateSystemCaretFor(aTarget, aCaretRect); + AccessibleWrap::FireWinEvent(WrapperFor(aTarget), + nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED); +} + +void a11y::ProxyTextChangeEvent(ProxyAccessible* aText, const nsString& aStr, + int32_t aStart, uint32_t aLen, bool aInsert, + bool) { + AccessibleWrap* wrapper = WrapperFor(aText); + MOZ_ASSERT(wrapper); + if (!wrapper) { + return; + } + + static const bool useHandler = + Preferences::GetBool("accessibility.handler.enabled", false) && + IsHandlerRegistered(); + + if (useHandler) { + wrapper->DispatchTextChangeToHandler(aInsert, aStr, aStart, aLen); + return; + } + + auto text = static_cast<HyperTextAccessibleWrap*>(wrapper->AsHyperText()); + if (text) { + ia2AccessibleText::UpdateTextChangeData(text, aInsert, aStr, aStart, aLen); + } + + uint32_t eventType = aInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED + : nsIAccessibleEvent::EVENT_TEXT_REMOVED; + AccessibleWrap::FireWinEvent(wrapper, eventType); +} + +void a11y::ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible*, + bool aInsert, bool) { + uint32_t event = + aInsert ? nsIAccessibleEvent::EVENT_SHOW : nsIAccessibleEvent::EVENT_HIDE; + AccessibleWrap* wrapper = WrapperFor(aTarget); + AccessibleWrap::FireWinEvent(wrapper, event); +} + +void a11y::ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible*, + uint32_t aType) { + AccessibleWrap* wrapper = WrapperFor(aTarget); + AccessibleWrap::FireWinEvent(wrapper, aType); +} + +bool a11y::IsHandlerRegistered() { + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + + nsAutoString clsid; + GUIDToString(CLSID_AccessibleHandler, clsid); + + nsAutoString subKey; + subKey.AppendLiteral(u"SOFTWARE\\Classes\\CLSID\\"); + subKey.Append(clsid); + subKey.AppendLiteral(u"\\InprocHandler32"); + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, subKey, + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + return false; + } + + nsAutoString handlerPath; + rv = regKey->ReadStringValue(nsAutoString(), handlerPath); + if (NS_FAILED(rv)) { + return false; + } + + nsCOMPtr<nsIFile> actualHandler; + rv = NS_NewLocalFile(handlerPath, false, getter_AddRefs(actualHandler)); + if (NS_FAILED(rv)) { + return false; + } + + nsCOMPtr<nsIFile> expectedHandler; + rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(expectedHandler)); + if (NS_FAILED(rv)) { + return false; + } + + rv = expectedHandler->Append(u"AccessibleHandler.dll"_ns); + if (NS_FAILED(rv)) { + return false; + } + + bool equal; + rv = expectedHandler->Equals(actualHandler, &equal); + return NS_SUCCEEDED(rv) && equal; +} + +static bool GetInstantiatorExecutable(const DWORD aPid, + nsIFile** aOutClientExe) { + nsAutoHandle callingProcess( + ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, aPid)); + if (!callingProcess) { + return false; + } + + DWORD bufLen = MAX_PATH; + UniquePtr<wchar_t[]> buf; + + while (true) { + buf = MakeUnique<wchar_t[]>(bufLen); + if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) { + break; + } + + DWORD lastError = ::GetLastError(); + MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER); + if (lastError != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + + bufLen *= 2; + } + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), false, + getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return false; + } + + file.forget(aOutClientExe); + return NS_SUCCEEDED(rv); +} + +#if defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) + +/** + * Appends version information in the format "|a.b.c.d". + * If there is no version information, we append nothing. + */ +static void AppendVersionInfo(nsIFile* aClientExe, nsAString& aStrToAppend) { + MOZ_ASSERT(!NS_IsMainThread()); + + LauncherResult<ModuleVersion> version = GetModuleVersion(aClientExe); + if (version.isErr()) { + return; + } + + uint16_t major, minor, patch, build; + Tie(major, minor, patch, build) = version.unwrap().AsTuple(); + + aStrToAppend.AppendLiteral(u"|"); + + constexpr auto dot = u"."_ns; + + aStrToAppend.AppendInt(major); + aStrToAppend.Append(dot); + aStrToAppend.AppendInt(minor); + aStrToAppend.Append(dot); + aStrToAppend.AppendInt(patch); + aStrToAppend.Append(dot); + aStrToAppend.AppendInt(build); +} + +static void AccumulateInstantiatorTelemetry(const nsAString& aValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aValue.IsEmpty()) { +# if defined(MOZ_TELEMETRY_REPORTING) + Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS, aValue); +# endif // defined(MOZ_TELEMETRY_REPORTING) +# if defined(MOZ_CRASHREPORTER) + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::AccessibilityClient, + NS_ConvertUTF16toUTF8(aValue)); +# endif // defined(MOZ_CRASHREPORTER) + } +} + +static void GatherInstantiatorTelemetry(nsIFile* aClientExe) { + MOZ_ASSERT(!NS_IsMainThread()); + + nsString value; + nsresult rv = aClientExe->GetLeafName(value); + if (NS_SUCCEEDED(rv)) { + AppendVersionInfo(aClientExe, value); + } + + nsCOMPtr<nsIRunnable> runnable( + NS_NewRunnableFunction("a11y::AccumulateInstantiatorTelemetry", + [value = std::move(value)]() -> void { + AccumulateInstantiatorTelemetry(value); + })); + + // Now that we've (possibly) obtained version info, send the resulting + // string back to the main thread to accumulate in telemetry. + NS_DispatchToMainThread(runnable.forget()); +} + +#endif // defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) + +void a11y::SetInstantiator(const uint32_t aPid) { + nsCOMPtr<nsIFile> clientExe; + if (!GetInstantiatorExecutable(aPid, getter_AddRefs(clientExe))) { +#if defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) + AccumulateInstantiatorTelemetry( + u"(Failed to retrieve client image name)"_ns); +#endif // defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) + return; + } + + // Only record the instantiator if it is the first instantiator, or if it does + // not match the previous one. Some blocked clients are repeatedly requesting + // a11y over and over so we don't want to be spawning countless telemetry + // threads. + if (gInstantiator) { + bool equal; + nsresult rv = gInstantiator->Equals(clientExe, &equal); + if (NS_SUCCEEDED(rv) && equal) { + return; + } + } + + gInstantiator = clientExe; + +#if defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) + nsCOMPtr<nsIRunnable> runnable( + NS_NewRunnableFunction("a11y::GatherInstantiatorTelemetry", + [clientExe = std::move(clientExe)]() -> void { + GatherInstantiatorTelemetry(clientExe); + })); + + DebugOnly<nsresult> rv = + NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +#endif // defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) +} + +bool a11y::GetInstantiator(nsIFile** aOutInstantiator) { + if (!gInstantiator) { + return false; + } + + return NS_SUCCEEDED(gInstantiator->Clone(aOutInstantiator)); +} diff --git a/accessible/windows/msaa/RootAccessibleWrap.cpp b/accessible/windows/msaa/RootAccessibleWrap.cpp new file mode 100644 index 0000000000..5ed0b9773f --- /dev/null +++ b/accessible/windows/msaa/RootAccessibleWrap.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "RootAccessibleWrap.h" + +#include "Compatibility.h" +#include "mozilla/PresShell.h" +#include "mozilla/WindowsVersion.h" +#include "nsCoreUtils.h" +#include "nsWinUtils.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// Constructor/destructor + +RootAccessibleWrap::RootAccessibleWrap(dom::Document* aDocument, + PresShell* aPresShell) + : RootAccessible(aDocument, aPresShell), mOuter(&mInternalUnknown) {} + +RootAccessibleWrap::~RootAccessibleWrap() {} + +//////////////////////////////////////////////////////////////////////////////// +// Aggregated IUnknown +HRESULT +RootAccessibleWrap::InternalQueryInterface(REFIID aIid, void** aOutInterface) { + if (!aOutInterface) { + return E_INVALIDARG; + } + + // InternalQueryInterface should always return its internal unknown + // when queried for IID_IUnknown... + if (aIid == IID_IUnknown) { + RefPtr<IUnknown> punk(&mInternalUnknown); + punk.forget(aOutInterface); + return S_OK; + } + + // ...Otherwise we pass through to the base COM implementation of + // QueryInterface which is provided by DocAccessibleWrap. + return DocAccessibleWrap::QueryInterface(aIid, aOutInterface); +} + +ULONG +RootAccessibleWrap::InternalAddRef() { return DocAccessible::AddRef(); } + +ULONG +RootAccessibleWrap::InternalRelease() { return DocAccessible::Release(); } + +already_AddRefed<IUnknown> RootAccessibleWrap::Aggregate(IUnknown* aOuter) { + MOZ_ASSERT(mOuter && + (mOuter == &mInternalUnknown || mOuter == aOuter || !aOuter)); + if (!aOuter) { + // If there is no aOuter then we should always set mOuter to + // mInternalUnknown. This is standard COM aggregation stuff. + mOuter = &mInternalUnknown; + return nullptr; + } + + mOuter = aOuter; + return GetInternalUnknown(); +} + +already_AddRefed<IUnknown> RootAccessibleWrap::GetInternalUnknown() { + RefPtr<IUnknown> result(&mInternalUnknown); + return result.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// RootAccessible + +void RootAccessibleWrap::DocumentActivated(DocAccessible* aDocument) { + // This check will never work with e10s enabled, in other words, as of + // Firefox 57. + if (Compatibility::IsDolphin() && + nsCoreUtils::IsTopLevelContentDocInProcess(aDocument->DocumentNode())) { + MOZ_ASSERT(XRE_IsParentProcess()); + uint32_t count = mChildDocuments.Length(); + for (uint32_t idx = 0; idx < count; idx++) { + DocAccessible* childDoc = mChildDocuments[idx]; + HWND childDocHWND = static_cast<HWND>(childDoc->GetNativeWindow()); + if (childDoc != aDocument) + nsWinUtils::HideNativeWindow(childDocHWND); + else + nsWinUtils::ShowNativeWindow(childDocHWND); + } + } +} + +STDMETHODIMP +RootAccessibleWrap::accNavigate( + /* [in] */ long navDir, + /* [optional][in] */ VARIANT varStart, + /* [retval][out] */ VARIANT __RPC_FAR* pvarEndUpAt) { + // Special handling for NAVRELATION_EMBEDS. + // When we only have a single process, this can be handled the same way as + // any other relation. + // However, for multi process, the normal relation mechanism doesn't work + // because it can't handle remote objects. + if (navDir != NAVRELATION_EMBEDS || varStart.vt != VT_I4 || + varStart.lVal != CHILDID_SELF) { + // We only handle EMBEDS on the root here. + // Forward to the base implementation. + return DocAccessibleWrap::accNavigate(navDir, varStart, pvarEndUpAt); + } + + if (!pvarEndUpAt) { + return E_INVALIDARG; + } + if (IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + Accessible* target = nullptr; + // Get the document in the active tab. + ProxyAccessible* docProxy = GetPrimaryRemoteTopLevelContentDoc(); + if (docProxy) { + target = WrapperFor(docProxy); + } else { + // The base implementation could handle this, but we may as well + // just handle it here. + Relation rel = RelationByType(RelationType::EMBEDS); + target = rel.Next(); + } + + if (!target) { + return E_FAIL; + } + + VariantInit(pvarEndUpAt); + pvarEndUpAt->pdispVal = NativeAccessible(target); + pvarEndUpAt->vt = VT_DISPATCH; + return S_OK; +} + +STDMETHODIMP +RootAccessibleWrap::get_accFocus( + /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) { + HRESULT hr = DocAccessibleWrap::get_accFocus(pvarChild); + if (FAILED(hr) || pvarChild->vt != VT_EMPTY || !IsWin8OrLater()) { + // 1. We got a definite result (either failure or an accessible); or + // 2. This is Windows 7, where we don't want to retrieve the focus from a + // remote document because this causes mysterious intermittent crashes + // when we're called by UIA clients; see bug 1424505. + return hr; + } + + // The base implementation reported no focus. + // Focus might be in a remote document. + // (The base implementation can't handle this.) + // Get the document in the active tab. + ProxyAccessible* docProxy = GetPrimaryRemoteTopLevelContentDoc(); + if (!docProxy) { + return hr; + } + Accessible* docAcc = WrapperFor(docProxy); + if (!docAcc) { + return E_FAIL; + } + RefPtr<IDispatch> docDisp = NativeAccessible(docAcc); + if (!docDisp) { + return E_FAIL; + } + RefPtr<IAccessible> docIa; + hr = docDisp->QueryInterface(IID_IAccessible, (void**)getter_AddRefs(docIa)); + MOZ_ASSERT(SUCCEEDED(hr)); + MOZ_ASSERT(docIa); + + // Ask this document for its focused descendant. + // We return this as is to the client except for CHILDID_SELF (see below). + hr = docIa->get_accFocus(pvarChild); + if (FAILED(hr)) { + return hr; + } + + if (pvarChild->vt == VT_I4 && pvarChild->lVal == CHILDID_SELF) { + // The document itself has focus. + // We're handling a call to accFocus on the root accessible, + // so replace CHILDID_SELF with the document accessible. + pvarChild->vt = VT_DISPATCH; + docDisp.forget(&pvarChild->pdispVal); + } + + return S_OK; +} diff --git a/accessible/windows/msaa/RootAccessibleWrap.h b/accessible/windows/msaa/RootAccessibleWrap.h new file mode 100644 index 0000000000..1db6a5557c --- /dev/null +++ b/accessible/windows/msaa/RootAccessibleWrap.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_RootAccessibleWrap_h__ +#define mozilla_a11y_RootAccessibleWrap_h__ + +#include "mozilla/mscom/Aggregation.h" +#include "RootAccessible.h" + +namespace mozilla { + +class PresShell; + +namespace a11y { + +class RootAccessibleWrap : public RootAccessible { + public: + RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell); + virtual ~RootAccessibleWrap(); + + // RootAccessible + virtual void DocumentActivated(DocAccessible* aDocument); + + /** + * This method enables a RootAccessibleWrap to be wrapped by a + * LazyInstantiator. + * + * @param aOuter The IUnknown of the object that is wrapping this + * RootAccessibleWrap, or nullptr to unwrap the aOuter from + * a previous call. + * @return This objects own IUnknown (as opposed to aOuter's IUnknown). + */ + already_AddRefed<IUnknown> Aggregate(IUnknown* aOuter); + + /** + * @return This object's own IUnknown, as opposed to its wrapper's IUnknown + * which is what would be returned by QueryInterface(IID_IUnknown). + */ + already_AddRefed<IUnknown> GetInternalUnknown(); + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE accNavigate( + /* [in] */ long navDir, + /* [optional][in] */ VARIANT varStart, + /* [retval][out] */ VARIANT __RPC_FAR* pvarEndUpAt) override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accFocus( + /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) override; + + private: + // DECLARE_AGGREGATABLE declares the internal IUnknown methods as well as + // mInternalUnknown. + DECLARE_AGGREGATABLE(RootAccessibleWrap); + IUnknown* mOuter; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/ServiceProvider.cpp b/accessible/windows/msaa/ServiceProvider.cpp new file mode 100644 index 0000000000..c552b9bac0 --- /dev/null +++ b/accessible/windows/msaa/ServiceProvider.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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 "ServiceProvider.h" + +#include "ApplicationAccessibleWrap.h" +#include "DocAccessible.h" +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "Relation.h" +#include "RootAccessible.h" +#include "uiaRawElmProvider.h" + +#include "mozilla/a11y/DocAccessibleChild.h" +#include "mozilla/Preferences.h" + +#include "ISimpleDOM.h" + +namespace mozilla { +namespace a11y { + +IMPL_IUNKNOWN_QUERY_HEAD(ServiceProvider) +IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// IServiceProvider + +STDMETHODIMP +ServiceProvider::QueryService(REFGUID aGuidService, REFIID aIID, + void** aInstancePtr) { + if (!aInstancePtr) return E_INVALIDARG; + + *aInstancePtr = nullptr; + + // UIA IAccessibleEx + if (aGuidService == IID_IAccessibleEx && + Preferences::GetBool("accessibility.uia.enable")) { + uiaRawElmProvider* accEx = new uiaRawElmProvider(mAccessible); + HRESULT hr = accEx->QueryInterface(aIID, aInstancePtr); + if (FAILED(hr)) delete accEx; + + return hr; + } + + // Provide a special service ID for getting the accessible for the browser tab + // document that contains this accessible object. If this accessible object + // is not inside a browser tab then the service fails with E_NOINTERFACE. + // A use case for this is for screen readers that need to switch context or + // 'virtual buffer' when focus moves from one browser tab area to another. + static const GUID SID_IAccessibleContentDocument = { + 0xa5d8e1f3, + 0x3571, + 0x4d8f, + {0x95, 0x21, 0x07, 0xed, 0x28, 0xfb, 0x07, 0x2e}}; + if (aGuidService == SID_IAccessibleContentDocument) { + if (aIID != IID_IAccessible) return E_NOINTERFACE; + + // If mAccessible is within an OOP iframe document, the top level document + // lives in a different process. + if (XRE_IsContentProcess()) { + RootAccessible* root = mAccessible->RootAccessible(); + // root will be null if mAccessible is the ApplicationAccessible. + if (root) { + DocAccessibleChild* ipcDoc = root->IPCDoc(); + if (ipcDoc) { + RefPtr<IAccessible> topDoc = ipcDoc->GetTopLevelDocIAccessible(); + // topDoc will be null if this isn't an OOP iframe document. + if (topDoc) { + topDoc.forget(aInstancePtr); + return S_OK; + } + } + } + } + + Relation rel = + mAccessible->RelationByType(RelationType::CONTAINING_TAB_PANE); + AccessibleWrap* tabDoc = static_cast<AccessibleWrap*>(rel.Next()); + if (!tabDoc) return E_NOINTERFACE; + + *aInstancePtr = static_cast<IAccessible*>(tabDoc); + (reinterpret_cast<IUnknown*>(*aInstancePtr))->AddRef(); + return S_OK; + } + + // Can get to IAccessibleApplication from any node via QS + // Note: in case of JAWS we want to check if aIID is + // IID_IAccessibleApplication. + if (aGuidService == IID_IAccessibleApplication || + aIID == IID_IAccessibleApplication) { + ApplicationAccessibleWrap* applicationAcc = + static_cast<ApplicationAccessibleWrap*>(ApplicationAcc()); + if (!applicationAcc) return E_NOINTERFACE; + + return applicationAcc->QueryInterface(aIID, aInstancePtr); + } + + static const GUID IID_SimpleDOMDeprecated = { + 0x0c539790, + 0x12e4, + 0x11cf, + {0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8}}; + if (aGuidService == IID_ISimpleDOMNode || + aGuidService == IID_SimpleDOMDeprecated || + aGuidService == IID_IAccessible || aGuidService == IID_IAccessible2) + return mAccessible->QueryInterface(aIID, aInstancePtr); + + return E_INVALIDARG; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/windows/msaa/ServiceProvider.h b/accessible/windows/msaa/ServiceProvider.h new file mode 100644 index 0000000000..82413aed21 --- /dev/null +++ b/accessible/windows/msaa/ServiceProvider.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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_ServiceProvider_h_ +#define mozilla_a11y_ServiceProvider_h_ + +#include <servprov.h> + +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" + +namespace mozilla { +namespace a11y { + +class ServiceProvider final : public IServiceProvider { + public: + explicit ServiceProvider(AccessibleWrap* aAcc) : mAccessible(aAcc) {} + ~ServiceProvider() {} + + DECL_IUNKNOWN + + // IServiceProvider + virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID aGuidService, + REFIID aIID, + void** aInstancePtr); + + private: + RefPtr<AccessibleWrap> mAccessible; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/TextLeafAccessibleWrap.cpp b/accessible/windows/msaa/TextLeafAccessibleWrap.cpp new file mode 100644 index 0000000000..52ece87db0 --- /dev/null +++ b/accessible/windows/msaa/TextLeafAccessibleWrap.cpp @@ -0,0 +1,21 @@ +/* -*- 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 "TextLeafAccessibleWrap.h" + +#include "sdnTextAccessible.h" +#include "Statistics.h" + +using namespace mozilla::a11y; + +IMPL_IUNKNOWN_QUERY_HEAD(TextLeafAccessibleWrap) +if (aIID == IID_ISimpleDOMText) { + statistics::ISimpleDOMUsed(); + *aInstancePtr = static_cast<ISimpleDOMText*>(new sdnTextAccessible(this)); + static_cast<IUnknown*>(*aInstancePtr)->AddRef(); + return S_OK; +} +IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(AccessibleWrap) diff --git a/accessible/windows/msaa/TextLeafAccessibleWrap.h b/accessible/windows/msaa/TextLeafAccessibleWrap.h new file mode 100644 index 0000000000..b22df460b5 --- /dev/null +++ b/accessible/windows/msaa/TextLeafAccessibleWrap.h @@ -0,0 +1,32 @@ +/* -*- 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_TextLeafAccessibleWrap_h__ +#define mozilla_a11y_TextLeafAccessibleWrap_h__ + +#include "TextLeafAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Wrap TextLeafAccessible to expose ISimpleDOMText as a native interface with + * a tear off. + */ +class TextLeafAccessibleWrap : public TextLeafAccessible { + public: + TextLeafAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : TextLeafAccessible(aContent, aDoc) {} + virtual ~TextLeafAccessibleWrap() {} + + // IUnknown + DECL_IUNKNOWN_INHERITED +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/XULListboxAccessibleWrap.cpp b/accessible/windows/msaa/XULListboxAccessibleWrap.cpp new file mode 100644 index 0000000000..c09ad91fcf --- /dev/null +++ b/accessible/windows/msaa/XULListboxAccessibleWrap.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "XULListboxAccessibleWrap.h" + +#include "Accessible-inl.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULListboxAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(XULListboxAccessibleWrap, XULListboxAccessible) + +IMPL_IUNKNOWN_QUERY_HEAD(XULListboxAccessibleWrap) +IMPL_IUNKNOWN_QUERY_CLASS_COND(ia2AccessibleTable, + !IsDefunct() && IsMulticolumn()); +IMPL_IUNKNOWN_QUERY_CLASS(AccessibleWrap) +IMPL_IUNKNOWN_QUERY_TAIL + +void XULListboxAccessibleWrap::Shutdown() { + ia2AccessibleTable::mTable = nullptr; + XULListboxAccessible::Shutdown(); +} diff --git a/accessible/windows/msaa/XULListboxAccessibleWrap.h b/accessible/windows/msaa/XULListboxAccessibleWrap.h new file mode 100644 index 0000000000..9b27edd43d --- /dev/null +++ b/accessible/windows/msaa/XULListboxAccessibleWrap.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_XULListboxAccessibleWrap_h__ +#define mozilla_a11y_XULListboxAccessibleWrap_h__ + +#include "XULListboxAccessible.h" + +#include "ia2AccessibleTable.h" + +namespace mozilla { +namespace a11y { + +/** + * IA2 wrapper class for XULListboxAccessible class implementing + * IAccessibleTable and IAccessibleTable2 interfaces. + */ +class XULListboxAccessibleWrap : public XULListboxAccessible, + public ia2AccessibleTable { + ~XULListboxAccessibleWrap() {} + + public: + XULListboxAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : XULListboxAccessible(aContent, aDoc), ia2AccessibleTable(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/XULMenuAccessibleWrap.cpp b/accessible/windows/msaa/XULMenuAccessibleWrap.cpp new file mode 100644 index 0000000000..dc9210dbb6 --- /dev/null +++ b/accessible/windows/msaa/XULMenuAccessibleWrap.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "AccessibleWrap.h" +#include "EnumVariant.h" +#include "XULMenuAccessibleWrap.h" +#include "nsNameSpaceManager.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +XULMenuitemAccessibleWrap::XULMenuitemAccessibleWrap(nsIContent* aContent, + DocAccessible* aDoc) + : XULMenuitemAccessible(aContent, aDoc) {} + +ENameValueFlag XULMenuitemAccessibleWrap::Name(nsString& aName) const { + // XXX This should be done in MSAA's get_accName() so that Accessible::Name()] + // provides the same results on all platforms + XULMenuitemAccessible::Name(aName); + if (aName.IsEmpty()) return eNameOK; + + nsAutoString accel; + if (mContent->IsElement()) { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, + accel); + } + if (!accel.IsEmpty()) aName += u"\t"_ns + accel; + + return eNameOK; +} + +STDMETHODIMP +XULMenuitemAccessibleWrap::get_accKeyboardShortcut( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) { + if (!pszKeyboardShortcut) return E_INVALIDARG; + *pszKeyboardShortcut = nullptr; + + if (varChild.vt != VT_I4 || varChild.lVal != CHILDID_SELF) { + return AccessibleWrap::get_accKeyboardShortcut(varChild, + pszKeyboardShortcut); + } + + if (IsDefunct()) { + return CO_E_OBJNOTCONNECTED; + } + + KeyBinding keyBinding = AccessKey(); + if (keyBinding.IsEmpty()) { + return S_FALSE; + } + + nsAutoString shortcut; + keyBinding.ToString(shortcut); + + *pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), shortcut.Length()); + return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY; +} diff --git a/accessible/windows/msaa/XULMenuAccessibleWrap.h b/accessible/windows/msaa/XULMenuAccessibleWrap.h new file mode 100644 index 0000000000..75d6da5ddd --- /dev/null +++ b/accessible/windows/msaa/XULMenuAccessibleWrap.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_XULMenuAccessibleWrap_h__ +#define mozilla_a11y_XULMenuAccessibleWrap_h__ + +#include "XULMenuAccessible.h" + +namespace mozilla { +namespace a11y { + +class XULMenuitemAccessibleWrap : public XULMenuitemAccessible { + public: + XULMenuitemAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc); + virtual ~XULMenuitemAccessibleWrap() {} + + // nsIAccessible + virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) const override; + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut( + /* [optional][in] */ VARIANT varChild, + /* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp b/accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp new file mode 100644 index 0000000000..ed1a2296b0 --- /dev/null +++ b/accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "XULTreeGridAccessibleWrap.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(XULTreeGridAccessibleWrap, XULTreeGridAccessible) + +IMPL_IUNKNOWN_INHERITED1(XULTreeGridAccessibleWrap, AccessibleWrap, + ia2AccessibleTable) + +void XULTreeGridAccessibleWrap::Shutdown() { + ia2AccessibleTable::mTable = nullptr; + XULTreeGridAccessible::Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// +// XULTreeGridCellAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(XULTreeGridCellAccessibleWrap, + XULTreeGridCellAccessible) + +IMPL_IUNKNOWN_INHERITED1(XULTreeGridCellAccessibleWrap, AccessibleWrap, + ia2AccessibleTableCell) + +void XULTreeGridCellAccessibleWrap::Shutdown() { + ia2AccessibleTableCell::mTableCell = nullptr; + XULTreeGridCellAccessible::Shutdown(); +} diff --git a/accessible/windows/msaa/XULTreeGridAccessibleWrap.h b/accessible/windows/msaa/XULTreeGridAccessibleWrap.h new file mode 100644 index 0000000000..7a014725c8 --- /dev/null +++ b/accessible/windows/msaa/XULTreeGridAccessibleWrap.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_XULTreeGridAccessibleWrap_h__ +#define mozilla_a11y_XULTreeGridAccessibleWrap_h__ + +#include "XULTreeGridAccessible.h" + +#include "ia2AccessibleTable.h" +#include "ia2AccessibleTableCell.h" + +namespace mozilla { +namespace a11y { + +/** + * IA2 wrapper class for XULTreeGridAccessible class implementing + * IAccessibleTable and IAccessibleTable2 interfaces. + */ +class XULTreeGridAccessibleWrap : public XULTreeGridAccessible, + public ia2AccessibleTable { + ~XULTreeGridAccessibleWrap() {} + + public: + XULTreeGridAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc, + nsTreeBodyFrame* aTree) + : XULTreeGridAccessible(aContent, aDoc, aTree), + ia2AccessibleTable(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +/** + * IA2 wrapper class for XULTreeGridCellAccessible class, implements + * IAccessibleTableCell interface. + */ +class XULTreeGridCellAccessibleWrap : public XULTreeGridCellAccessible, + public ia2AccessibleTableCell { + ~XULTreeGridCellAccessibleWrap() {} + + public: + XULTreeGridCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc, + XULTreeGridRowAccessible* aRowAcc, + dom::XULTreeElement* aTree, + nsITreeView* aTreeView, int32_t aRow, + nsTreeColumn* aColumn) + : XULTreeGridCellAccessible(aContent, aDoc, aRowAcc, aTree, aTreeView, + aRow, aColumn), + ia2AccessibleTableCell(this) {} + + // IUnknown + DECL_IUNKNOWN_INHERITED + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + virtual void Shutdown() override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/msaa/moz.build b/accessible/windows/msaa/moz.build new file mode 100644 index 0000000000..5b8163f2fd --- /dev/null +++ b/accessible/windows/msaa/moz.build @@ -0,0 +1,85 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "IUnknownImpl.h", +] + +EXPORTS.mozilla.a11y += [ + "AccessibleWrap.h", + "Compatibility.h", + "HyperTextAccessibleWrap.h", + "LazyInstantiator.h", + "MsaaIdGenerator.h", + "nsWinUtils.h", +] + +UNIFIED_SOURCES += [ + "AccessibleWrap.cpp", + "ApplicationAccessibleWrap.cpp", + "ARIAGridAccessibleWrap.cpp", + "Compatibility.cpp", + "CompatibilityUIA.cpp", + "DocAccessibleWrap.cpp", + "EnumVariant.cpp", + "GeckoCustom.cpp", + "HTMLTableAccessibleWrap.cpp", + "HTMLWin32ObjectAccessible.cpp", + "HyperTextAccessibleWrap.cpp", + "ImageAccessibleWrap.cpp", + "IUnknownImpl.cpp", + "MsaaIdGenerator.cpp", + "nsWinUtils.cpp", + "Platform.cpp", + "RootAccessibleWrap.cpp", + "TextLeafAccessibleWrap.cpp", +] + +SOURCES += [ + # This file cannot be built in unified mode because it redefines _WIN32_WINNT + "LazyInstantiator.cpp", + # This file cannot be built in unified mode because it includes ISimpleDOMNode_i.c. + "ServiceProvider.cpp", +] + +OS_LIBS += [ + "ntdll", +] + +if CONFIG["MOZ_XUL"]: + UNIFIED_SOURCES += [ + "XULListboxAccessibleWrap.cpp", + "XULMenuAccessibleWrap.cpp", + "XULTreeGridAccessibleWrap.cpp", + ] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/ipc", + "/accessible/ipc/win", + "/accessible/windows", + "/accessible/windows/ia2", + "/accessible/windows/sdn", + "/accessible/windows/uia", + "/accessible/xpcom", + "/accessible/xul", + "/dom/base", + "/layout/style", +] + +# The Windows MIDL code generator creates things like: +# +# #endif !_MIDL_USE_GUIDDEF_ +# +# which clang-cl complains about. MSVC doesn't, so turn this warning off. +if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += ["-Wno-extra-tokens"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/windows/msaa/nsEventMap.h b/accessible/windows/msaa/nsEventMap.h new file mode 100644 index 0000000000..ee492c37e4 --- /dev/null +++ b/accessible/windows/msaa/nsEventMap.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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 <winuser.h> +#include "AccessibleEventId.h" + +const uint32_t kEVENT_WIN_UNKNOWN = 0x00000000; + +static const uint32_t gWinEventMap[] = { + // clang-format off + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent doesn't have 0 constant + EVENT_OBJECT_SHOW, // nsIAccessibleEvent::EVENT_SHOW + EVENT_OBJECT_HIDE, // nsIAccessibleEvent::EVENT_HIDE + EVENT_OBJECT_REORDER, // nsIAccessibleEvent::EVENT_REORDER + IA2_EVENT_ACTIVE_DECENDENT_CHANGED, // nsIAccessibleEvent::EVENT_ACTIVE_DECENDENT_CHANGED + EVENT_OBJECT_FOCUS, // nsIAccessibleEvent::EVENT_FOCUS + EVENT_OBJECT_STATECHANGE, // nsIAccessibleEvent::EVENT_STATE_CHANGE + EVENT_OBJECT_LOCATIONCHANGE, // nsIAccessibleEvent::EVENT_LOCATION_CHANGE + EVENT_OBJECT_NAMECHANGE, // nsIAccessibleEvent::EVENT_NAME_CHANGE + EVENT_OBJECT_DESCRIPTIONCHANGE, // nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE + EVENT_OBJECT_VALUECHANGE, // nsIAccessibleEvent::EVENT_VALUE_CHANGE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_HELP_CHANGE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_DEFACTION_CHANGE + IA2_EVENT_ACTION_CHANGED, // nsIAccessibleEvent::EVENT_ACTION_CHANGE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_ACCELERATOR_CHANGE + EVENT_OBJECT_SELECTION, // nsIAccessibleEvent::EVENT_SELECTION + EVENT_OBJECT_SELECTIONADD, // nsIAccessibleEvent::EVENT_SELECTION_ADD + EVENT_OBJECT_SELECTIONREMOVE, // nsIAccessibleEvent::EVENT_SELECTION_REMOVE + EVENT_OBJECT_SELECTIONWITHIN, // nsIAccessibleEvent::EVENT_SELECTION_WITHIN + EVENT_SYSTEM_ALERT, // nsIAccessibleEvent::EVENT_ALERT + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_FOREGROUND + EVENT_SYSTEM_MENUSTART, // nsIAccessibleEvent::EVENT_MENU_START + EVENT_SYSTEM_MENUEND, // nsIAccessibleEvent::EVENT_MENU_END + EVENT_SYSTEM_MENUPOPUPSTART, // nsIAccessibleEvent::EVENT_MENUPOPUP_START + EVENT_SYSTEM_MENUPOPUPEND, // nsIAccessibleEvent::EVENT_MENUPOPUP_END + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CAPTURE_START + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CAPTURE_END + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MOVESIZE_START + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MOVESIZE_END + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CONTEXT_HELP_START + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CONTEXT_HELP_END + EVENT_SYSTEM_DRAGDROPSTART, // nsIAccessibleEvent::EVENT_DRAGDROP_START + EVENT_SYSTEM_DRAGDROPEND, // nsIAccessibleEvent::EVENT_DRAGDROP_END + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_DIALOG_START + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_DIALOG_END + EVENT_SYSTEM_SCROLLINGSTART, // nsIAccessibleEvent::EVENT_SCROLLING_START + EVENT_SYSTEM_SCROLLINGEND, // nsIAccessibleEvent::EVENT_SCROLLING_END + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MINIMIZE_START + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MINIMIZE_END + IA2_EVENT_DOCUMENT_LOAD_COMPLETE, // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE + IA2_EVENT_DOCUMENT_RELOAD, // nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD + IA2_EVENT_DOCUMENT_LOAD_STOPPED, // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED + IA2_EVENT_DOCUMENT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_ATTRIBUTES_CHANGED + IA2_EVENT_DOCUMENT_CONTENT_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_CONTENT_CHANGED + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_PROPERTY_CHANGED + IA2_EVENT_PAGE_CHANGED, // nsIAccessibleEvent::IA2_EVENT_PAGE_CHANGED + IA2_EVENT_TEXT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED + IA2_EVENT_TEXT_CARET_MOVED, // nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED + IA2_EVENT_TEXT_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_CHANGED + IA2_EVENT_TEXT_INSERTED, // nsIAccessibleEvent::EVENT_TEXT_INSERTED + IA2_EVENT_TEXT_REMOVED, // nsIAccessibleEvent::EVENT_TEXT_REMOVED + IA2_EVENT_TEXT_UPDATED, // nsIAccessibleEvent::EVENT_TEXT_UPDATED + IA2_EVENT_TEXT_SELECTION_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED + IA2_EVENT_VISIBLE_DATA_CHANGED, // nsIAccessibleEvent::EVENT_VISIBLE_DATA_CHANGED + IA2_EVENT_TEXT_COLUMN_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_COLUMN_CHANGED + IA2_EVENT_SECTION_CHANGED, // nsIAccessibleEvent::EVENT_SECTION_CHANGED + IA2_EVENT_TABLE_CAPTION_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_CAPTION_CHANGED + IA2_EVENT_TABLE_MODEL_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED + IA2_EVENT_TABLE_SUMMARY_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_SUMMARY_CHANGED + IA2_EVENT_TABLE_ROW_DESCRIPTION_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_ROW_DESCRIPTION_CHANGED + IA2_EVENT_TABLE_ROW_HEADER_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_ROW_HEADER_CHANGED + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_ROW_INSERT + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_ROW_DELETE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_ROW_REORDER + IA2_EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED + IA2_EVENT_TABLE_COLUMN_HEADER_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_HEADER_CHANGED + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_INSERT + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_DELETE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_REORDER + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_CREATE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_DESTROY + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_RESIZE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_RESTORE + IA2_EVENT_HYPERLINK_END_INDEX_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_END_INDEX_CHANGED + IA2_EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED + IA2_EVENT_HYPERLINK_SELECTED_LINK_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_SELECTED_LINK_CHANGED + IA2_EVENT_HYPERTEXT_LINK_ACTIVATED, // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_ACTIVATED + IA2_EVENT_HYPERTEXT_LINK_SELECTED, // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_SELECTED + IA2_EVENT_HYPERLINK_START_INDEX_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_START_INDEX_CHANGED + IA2_EVENT_HYPERTEXT_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED + IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED + IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED + EVENT_OBJECT_VALUECHANGE, // nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_SCROLLING + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_ANNOUNCEMENT + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED + // clang-format on +}; diff --git a/accessible/windows/msaa/nsWinUtils.cpp b/accessible/windows/msaa/nsWinUtils.cpp new file mode 100644 index 0000000000..e037e76386 --- /dev/null +++ b/accessible/windows/msaa/nsWinUtils.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 "nsWinUtils.h" + +#include "Compatibility.h" +#include "DocAccessible.h" +#include "nsAccessibilityService.h" +#include "nsCoreUtils.h" + +#include "mozilla/a11y/DocAccessibleParent.h" +#include "mozilla/Preferences.h" +#include "nsArrayUtils.h" +#include "nsICSSDeclaration.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "nsXULAppAPI.h" +#include "ProxyWrappers.h" + +using namespace mozilla; +using namespace mozilla::a11y; +using mozilla::dom::Element; + +// Window property used by ipc related code in identifying accessible +// tab windows. +const wchar_t* kPropNameTabContent = L"AccessibleTabWindow"; + +/** + * WindowProc to process WM_GETOBJECT messages, used in windows emulation mode. + */ +static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam); + +bool nsWinUtils::sWindowEmulationStarted = false; + +already_AddRefed<nsICSSDeclaration> nsWinUtils::GetComputedStyleDeclaration( + nsIContent* aContent) { + nsIContent* elm = nsCoreUtils::GetDOMElementFor(aContent); + if (!elm) return nullptr; + + // Returns number of items in style declaration + nsCOMPtr<nsPIDOMWindowInner> window = elm->OwnerDoc()->GetInnerWindow(); + if (!window) return nullptr; + + ErrorResult dummy; + nsCOMPtr<Element> domElement(do_QueryInterface(elm)); + nsCOMPtr<nsICSSDeclaration> cssDecl = + window->GetComputedStyle(*domElement, u""_ns, dummy); + dummy.SuppressException(); + return cssDecl.forget(); +} + +bool nsWinUtils::MaybeStartWindowEmulation() { + // Register window class that'll be used for document accessibles associated + // with tabs. + if (IPCAccessibilityActive()) return false; + + if (Compatibility::IsJAWS() || Compatibility::IsWE() || + Compatibility::IsDolphin() || Compatibility::IsVisperoShared()) { + RegisterNativeWindow(kClassNameTabContent); + sWindowEmulationStarted = true; + return true; + } + + return false; +} + +void nsWinUtils::ShutdownWindowEmulation() { + // Unregister window call that's used for document accessibles associated + // with tabs. + if (IsWindowEmulationStarted()) { + ::UnregisterClassW(kClassNameTabContent, GetModuleHandle(nullptr)); + sWindowEmulationStarted = false; + } +} + +void nsWinUtils::RegisterNativeWindow(LPCWSTR aWindowClass) { + WNDCLASSW wc; + wc.style = CS_GLOBALCLASS; + wc.lpfnWndProc = WindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = aWindowClass; + ::RegisterClassW(&wc); +} + +HWND nsWinUtils::CreateNativeWindow(LPCWSTR aWindowClass, HWND aParentWnd, + int aX, int aY, int aWidth, int aHeight, + bool aIsActive, + NativeWindowCreateProc* aOnCreateProc) { + return ::CreateWindowExW( + WS_EX_TRANSPARENT, aWindowClass, L"NetscapeDispatchWnd", + WS_CHILD | (aIsActive ? WS_VISIBLE : 0), aX, aY, aWidth, aHeight, + aParentWnd, nullptr, GetModuleHandle(nullptr), aOnCreateProc); +} + +void nsWinUtils::ShowNativeWindow(HWND aWnd) { ::ShowWindow(aWnd, SW_SHOW); } + +void nsWinUtils::HideNativeWindow(HWND aWnd) { + ::SetWindowPos( + aWnd, nullptr, 0, 0, 0, 0, + SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); +} + +LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + // Note, this window's message handling should not invoke any call that + // may result in a cross-process ipc call. Doing so may violate RPC + // message semantics. + + switch (msg) { + case WM_CREATE: { + // Mark this window so that ipc related code can identify it. + ::SetPropW(hWnd, kPropNameTabContent, reinterpret_cast<HANDLE>(1)); + + auto createStruct = reinterpret_cast<CREATESTRUCT*>(lParam); + auto createProc = reinterpret_cast<nsWinUtils::NativeWindowCreateProc*>( + createStruct->lpCreateParams); + + if (createProc && *createProc) { + (*createProc)(hWnd); + } + + return 0; + } + case WM_GETOBJECT: { + // Do explicit casting to make it working on 64bit systems (see bug 649236 + // for details). + int32_t objId = static_cast<DWORD>(lParam); + if (objId == OBJID_CLIENT) { + IAccessible* msaaAccessible = nullptr; + DocAccessible* document = + reinterpret_cast<DocAccessible*>(::GetPropW(hWnd, kPropNameDocAcc)); + if (document) { + document->GetNativeInterface( + (void**)&msaaAccessible); // does an addref + } else { + DocAccessibleParent* docParent = static_cast<DocAccessibleParent*>( + ::GetPropW(hWnd, kPropNameDocAccParent)); + if (docParent) { + auto wrapper = WrapperFor(docParent); + wrapper->GetNativeInterface( + (void**)&msaaAccessible); // does an addref + } + } + if (msaaAccessible) { + LRESULT result = + ::LresultFromObject(IID_IAccessible, wParam, + msaaAccessible); // does an addref + msaaAccessible->Release(); // release extra addref + return result; + } + } + return 0; + } + case WM_NCHITTEST: { + LRESULT lRet = ::DefWindowProc(hWnd, msg, wParam, lParam); + if (HTCLIENT == lRet) lRet = HTTRANSPARENT; + return lRet; + } + } + + return ::DefWindowProcW(hWnd, msg, wParam, lParam); +} diff --git a/accessible/windows/msaa/nsWinUtils.h b/accessible/windows/msaa/nsWinUtils.h new file mode 100644 index 0000000000..7ae0cadc5d --- /dev/null +++ b/accessible/windows/msaa/nsWinUtils.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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 nsWinUtils_h_ +#define nsWinUtils_h_ + +#include <functional> +#include <windows.h> + +#include "nsICSSDeclaration.h" +#include "nsCOMPtr.h" + +class nsIContent; + +namespace mozilla { +namespace a11y { + +class DocAccessible; + +const LPCWSTR kClassNameRoot = L"MozillaUIWindowClass"; +const LPCWSTR kClassNameTabContent = L"MozillaContentWindowClass"; +const LPCWSTR kPropNameDocAcc = L"MozDocAccessible"; +const LPCWSTR kPropNameDocAccParent = L"MozDocAccessibleParent"; + +class nsWinUtils { + public: + /** + * Return computed styles declaration for the given node. + * + * @note Please use it carefully since it can shutdown the accessible tree + * you operate on. + */ + static already_AddRefed<nsICSSDeclaration> GetComputedStyleDeclaration( + nsIContent* aContent); + + /** + * Start window emulation if presence of specific AT is detected. + */ + static bool MaybeStartWindowEmulation(); + + /** + * Free resources used for window emulation. + */ + static void ShutdownWindowEmulation(); + + /** + * Return true if window emulation is started. + */ + static bool IsWindowEmulationStarted() { return sWindowEmulationStarted; } + + /** + * Helper to register window class. + */ + static void RegisterNativeWindow(LPCWSTR aWindowClass); + + typedef std::function<void(HWND)> NativeWindowCreateProc; + + /** + * Helper to create a window. + * + * NB: If additional setup needs to be done once the window has been created, + * you should do so via aOnCreateProc. Hooks will fire during the + * CreateNativeWindow call, thus triggering events in the AT. + * Using aOnCreateProc guarantees that your additional initialization will + * have completed prior to the AT receiving window creation events. + * + * For example: + * + * nsWinUtils::NativeWindowCreateProc onCreate([](HWND aHwnd) -> void { + * DoSomeAwesomeInitializationStuff(aHwnd); + * DoMoreAwesomeInitializationStuff(aHwnd); + * }); + * HWND hwnd = nsWinUtils::CreateNativeWindow(..., &onCreate); + * // Doing further initialization work to hwnd on this line is too late! + */ + static HWND CreateNativeWindow( + LPCWSTR aWindowClass, HWND aParentWnd, int aX, int aY, int aWidth, + int aHeight, bool aIsActive, + NativeWindowCreateProc* aOnCreateProc = nullptr); + + /** + * Helper to show window. + */ + static void ShowNativeWindow(HWND aWnd); + + /** + * Helper to hide window. + */ + static void HideNativeWindow(HWND aWnd); + + private: + /** + * Flag that indicates if window emulation is started. + */ + static bool sWindowEmulationStarted; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/sdn/moz.build b/accessible/windows/sdn/moz.build new file mode 100644 index 0000000000..8d76b7482e --- /dev/null +++ b/accessible/windows/sdn/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "sdnAccessible.cpp", + "sdnDocAccessible.cpp", + "sdnTextAccessible.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/windows/msaa", + "/accessible/xpcom", + "/accessible/xul", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/windows/sdn/sdnAccessible-inl.h b/accessible/windows/sdn/sdnAccessible-inl.h new file mode 100644 index 0000000000..ca949e92ec --- /dev/null +++ b/accessible/windows/sdn/sdnAccessible-inl.h @@ -0,0 +1,42 @@ +/* -*- 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_sdnAccessible_inl_h_ +#define mozilla_a11y_sdnAccessible_inl_h_ + +#include "sdnAccessible.h" + +#include "DocAccessible.h" +#include "nsAccessibilityService.h" + +namespace mozilla { +namespace a11y { + +inline DocAccessible* sdnAccessible::GetDocument() const { + return GetExistingDocAccessible(mNode->OwnerDoc()); +} + +inline AccessibleWrap* sdnAccessible::GetAccessible() { + if (mWrap) { + return mWrap; + } + + DocAccessible* document = GetDocument(); + if (!document) { + return nullptr; + } + + // Once we have an accessible, we should hold a reference to it so that we + // may preserve object identity. + mWrap = static_cast<AccessibleWrap*>( + document->GetAccessibleEvenIfNotInMap(mNode)); + return mWrap; +} + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_sdnAccessible_inl_h_ diff --git a/accessible/windows/sdn/sdnAccessible.cpp b/accessible/windows/sdn/sdnAccessible.cpp new file mode 100644 index 0000000000..04643e229a --- /dev/null +++ b/accessible/windows/sdn/sdnAccessible.cpp @@ -0,0 +1,440 @@ +/* -*- 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 "sdnAccessible-inl.h" +#include "ISimpleDOM_i.c" + +#include "DocAccessibleWrap.h" + +#include "nsAttrName.h" +#include "nsCoreUtils.h" +#include "nsIAccessibleTypes.h" +#include "nsICSSDeclaration.h" +#include "nsNameSpaceManager.h" +#include "nsServiceManagerUtils.h" +#include "nsWinUtils.h" +#include "nsRange.h" + +#include "mozilla/dom/BorrowedAttrInfo.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/PresShell.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +sdnAccessible::~sdnAccessible() { + if (mUniqueId.isSome()) { + AccessibleWrap::ReleaseChildID(WrapNotNull(this)); + } +} + +STDMETHODIMP +sdnAccessible::QueryInterface(REFIID aREFIID, void** aInstancePtr) { + if (!aInstancePtr) return E_FAIL; + *aInstancePtr = nullptr; + + if (aREFIID == IID_IClientSecurity) { + // Some code might QI(IID_IClientSecurity) to detect whether or not we are + // a proxy. Right now that can potentially happen off the main thread, so we + // look for this condition immediately so that we don't trigger other code + // that might not be thread-safe. + return E_NOINTERFACE; + } + + if (aREFIID == IID_ISimpleDOMNode) { + *aInstancePtr = static_cast<ISimpleDOMNode*>(this); + AddRef(); + return S_OK; + } + + AccessibleWrap* accessible = GetAccessible(); + if (accessible) return accessible->QueryInterface(aREFIID, aInstancePtr); + + // IUnknown* is the canonical one if and only if this accessible doesn't have + // an accessible. + if (aREFIID == IID_IUnknown) { + *aInstancePtr = static_cast<ISimpleDOMNode*>(this); + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP +sdnAccessible::get_nodeInfo(BSTR __RPC_FAR* aNodeName, + short __RPC_FAR* aNameSpaceID, + BSTR __RPC_FAR* aNodeValue, + unsigned int __RPC_FAR* aNumChildren, + unsigned int __RPC_FAR* aUniqueID, + unsigned short __RPC_FAR* aNodeType) { + if (!aNodeName || !aNameSpaceID || !aNodeValue || !aNumChildren || + !aUniqueID || !aNodeType) + return E_INVALIDARG; + + *aNodeName = nullptr; + *aNameSpaceID = 0; + *aNodeValue = nullptr; + *aNumChildren = 0; + *aUniqueID = 0; + *aNodeType = 0; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + uint16_t nodeType = mNode->NodeType(); + *aNodeType = static_cast<unsigned short>(nodeType); + + if (*aNodeType != NODETYPE_TEXT) { + *aNodeName = ::SysAllocString(mNode->NodeName().get()); + } + + nsAutoString nodeValue; + mNode->GetNodeValue(nodeValue); + *aNodeValue = ::SysAllocString(nodeValue.get()); + + *aNameSpaceID = mNode->IsContent() + ? static_cast<short>(mNode->AsContent()->GetNameSpaceID()) + : 0; + + // This is a unique ID for every content node. The 3rd party accessibility + // application can compare this to the childID we return for events such as + // focus events, to correlate back to data nodes in their internal object + // model. + AccessibleWrap* accessible = GetAccessible(); + if (accessible) { + *aUniqueID = AccessibleWrap::GetChildIDFor(accessible); + } else { + if (mUniqueId.isNothing()) { + AccessibleWrap::AssignChildIDTo(WrapNotNull(this)); + } + MOZ_ASSERT(mUniqueId.isSome()); + *aUniqueID = mUniqueId.value(); + } + + *aNumChildren = mNode->GetChildCount(); + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_attributes(unsigned short aMaxAttribs, + BSTR __RPC_FAR* aAttribNames, + short __RPC_FAR* aNameSpaceIDs, + BSTR __RPC_FAR* aAttribValues, + unsigned short __RPC_FAR* aNumAttribs) { + if (!aAttribNames || !aNameSpaceIDs || !aAttribValues || !aNumAttribs) + return E_INVALIDARG; + + *aNumAttribs = 0; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsElement()) return S_FALSE; + + dom::Element* elm = mNode->AsElement(); + uint32_t numAttribs = elm->GetAttrCount(); + if (numAttribs > aMaxAttribs) numAttribs = aMaxAttribs; + + *aNumAttribs = static_cast<unsigned short>(numAttribs); + + for (uint32_t index = 0; index < numAttribs; index++) { + aNameSpaceIDs[index] = 0; + aAttribValues[index] = aAttribNames[index] = nullptr; + nsAutoString attributeValue; + + dom::BorrowedAttrInfo attr = elm->GetAttrInfoAt(index); + attr.mValue->ToString(attributeValue); + + aNameSpaceIDs[index] = static_cast<short>(attr.mName->NamespaceID()); + aAttribNames[index] = + ::SysAllocString(attr.mName->LocalName()->GetUTF16String()); + aAttribValues[index] = ::SysAllocString(attributeValue.get()); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_attributesForNames(unsigned short aMaxAttribs, + BSTR __RPC_FAR* aAttribNames, + short __RPC_FAR* aNameSpaceID, + BSTR __RPC_FAR* aAttribValues) { + if (!aAttribNames || !aNameSpaceID || !aAttribValues) return E_INVALIDARG; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsElement()) return S_FALSE; + + dom::Element* domElement = mNode->AsElement(); + nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance(); + + int32_t index = 0; + for (index = 0; index < aMaxAttribs; index++) { + aAttribValues[index] = nullptr; + if (aAttribNames[index]) { + nsAutoString attributeValue, nameSpaceURI; + nsAutoString attributeName( + nsDependentString(static_cast<const wchar_t*>(aAttribNames[index]))); + + if (aNameSpaceID[index] > 0 && + NS_SUCCEEDED(nameSpaceManager->GetNameSpaceURI(aNameSpaceID[index], + nameSpaceURI))) { + domElement->GetAttributeNS(nameSpaceURI, attributeName, attributeValue); + } else { + domElement->GetAttribute(attributeName, attributeValue); + } + + aAttribValues[index] = ::SysAllocString(attributeValue.get()); + } + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_computedStyle( + unsigned short aMaxStyleProperties, boolean aUseAlternateView, + BSTR __RPC_FAR* aStyleProperties, BSTR __RPC_FAR* aStyleValues, + unsigned short __RPC_FAR* aNumStyleProperties) { + if (!aStyleProperties || aStyleValues || !aNumStyleProperties) + return E_INVALIDARG; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aNumStyleProperties = 0; + + if (mNode->IsDocument()) return S_FALSE; + + nsCOMPtr<nsICSSDeclaration> cssDecl = + nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent()); + NS_ENSURE_TRUE(cssDecl, E_FAIL); + + uint32_t length = cssDecl->Length(); + + uint32_t index = 0, realIndex = 0; + for (index = realIndex = 0; index < length && realIndex < aMaxStyleProperties; + index++) { + nsAutoCString property; + nsAutoCString value; + + // Ignore -moz-* properties. + cssDecl->Item(index, property); + if (property.CharAt(0) != '-') + cssDecl->GetPropertyValue(property, value); // Get property value + + if (!value.IsEmpty()) { + aStyleProperties[realIndex] = + ::SysAllocString(NS_ConvertUTF8toUTF16(property).get()); + aStyleValues[realIndex] = + ::SysAllocString(NS_ConvertUTF8toUTF16(value).get()); + ++realIndex; + } + } + + *aNumStyleProperties = static_cast<unsigned short>(realIndex); + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_computedStyleForProperties( + unsigned short aNumStyleProperties, boolean aUseAlternateView, + BSTR __RPC_FAR* aStyleProperties, BSTR __RPC_FAR* aStyleValues) { + if (!aStyleProperties || !aStyleValues) return E_INVALIDARG; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (mNode->IsDocument()) return S_FALSE; + + nsCOMPtr<nsICSSDeclaration> cssDecl = + nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent()); + NS_ENSURE_TRUE(cssDecl, E_FAIL); + + uint32_t index = 0; + for (index = 0; index < aNumStyleProperties; index++) { + nsAutoCString value; + if (aStyleProperties[index]) + cssDecl->GetPropertyValue( + NS_ConvertUTF16toUTF8(nsDependentString(aStyleProperties[index])), + value); // Get property value + aStyleValues[index] = ::SysAllocString(NS_ConvertUTF8toUTF16(value).get()); + } + + return S_OK; +} + +// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294. +MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP +sdnAccessible::scrollTo(boolean aScrollTopLeft) { + DocAccessible* document = GetDocument(); + if (!document) // that's IsDefunct check + return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsContent()) return S_FALSE; + + uint32_t scrollType = aScrollTopLeft + ? nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT + : nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT; + + RefPtr<PresShell> presShell = document->PresShellPtr(); + nsCOMPtr<nsIContent> content = mNode->AsContent(); + nsCoreUtils::ScrollTo(presShell, content, scrollType); + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_parentNode(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) { + if (!aNode) return E_INVALIDARG; + *aNode = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetParentNode(); + if (resultNode) { + *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode)); + (*aNode)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_firstChild(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) { + if (!aNode) return E_INVALIDARG; + *aNode = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetFirstChild(); + if (resultNode) { + *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode)); + (*aNode)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_lastChild(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) { + if (!aNode) return E_INVALIDARG; + *aNode = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetLastChild(); + if (resultNode) { + *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode)); + (*aNode)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_previousSibling(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) { + if (!aNode) return E_INVALIDARG; + *aNode = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetPreviousSibling(); + if (resultNode) { + *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode)); + (*aNode)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_nextSibling(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) { + if (!aNode) return E_INVALIDARG; + *aNode = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetNextSibling(); + if (resultNode) { + *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode)); + (*aNode)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_childAt(unsigned aChildIndex, + ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) { + if (!aNode) return E_INVALIDARG; + *aNode = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsINode* resultNode = mNode->GetChildAt_Deprecated(aChildIndex); + if (resultNode) { + *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode)); + (*aNode)->AddRef(); + } + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_innerHTML(BSTR __RPC_FAR* aInnerHTML) { + if (!aInnerHTML) return E_INVALIDARG; + *aInnerHTML = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (!mNode->IsElement()) return S_FALSE; + + nsAutoString innerHTML; + mNode->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors()); + if (innerHTML.IsEmpty()) return S_FALSE; + + *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length()); + if (!*aInnerHTML) return E_OUTOFMEMORY; + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_localInterface(void __RPC_FAR* __RPC_FAR* aLocalInterface) { + if (!aLocalInterface) return E_INVALIDARG; + *aLocalInterface = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aLocalInterface = this; + AddRef(); + + return S_OK; +} + +STDMETHODIMP +sdnAccessible::get_language(BSTR __RPC_FAR* aLanguage) { + if (!aLanguage) return E_INVALIDARG; + *aLanguage = nullptr; + + if (IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString language; + if (mNode->IsContent()) + nsCoreUtils::GetLanguageFor(mNode->AsContent(), nullptr, language); + if (language.IsEmpty()) { // Nothing found, so use document's language + mNode->OwnerDoc()->GetHeaderData(nsGkAtoms::headerContentLanguage, + language); + } + + if (language.IsEmpty()) return S_FALSE; + + *aLanguage = ::SysAllocStringLen(language.get(), language.Length()); + if (!*aLanguage) return E_OUTOFMEMORY; + + return S_OK; +} diff --git a/accessible/windows/sdn/sdnAccessible.h b/accessible/windows/sdn/sdnAccessible.h new file mode 100644 index 0000000000..964fa8ad9c --- /dev/null +++ b/accessible/windows/sdn/sdnAccessible.h @@ -0,0 +1,132 @@ +/* -*- 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_sdnAccessible_h_ +#define mozilla_a11y_sdnAccessible_h_ + +#include "ISimpleDOM.h" +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/NotNull.h" + +namespace mozilla { +namespace a11y { + +class sdnAccessible final : public ISimpleDOMNode { + public: + explicit sdnAccessible(nsINode* aNode) : mNode(aNode) { + if (!mNode) MOZ_CRASH(); + } + + explicit sdnAccessible(NotNull<AccessibleWrap*> aAccWrap) + : mNode(aAccWrap->GetNode()), mWrap(aAccWrap) {} + + ~sdnAccessible(); + + /** + * Return if the object is defunct. + */ + bool IsDefunct() const { return !GetDocument(); } + + /** + * Return a document accessible it belongs to if any. + */ + DocAccessible* GetDocument() const; + + /* + * Return associated accessible if any. + */ + AccessibleWrap* GetAccessible(); + + void SetUniqueID(uint32_t aNewUniqueId) { mUniqueId = Some(aNewUniqueId); } + + Maybe<uint32_t> ReleaseUniqueID() { + Maybe<uint32_t> result = mUniqueId; + mUniqueId = Nothing(); + return result; + } + + // IUnknown + DECL_IUNKNOWN + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nodeInfo( + /* [out] */ BSTR __RPC_FAR* aNodeName, + /* [out] */ short __RPC_FAR* aNameSpaceID, + /* [out] */ BSTR __RPC_FAR* aNodeValue, + /* [out] */ unsigned int __RPC_FAR* aNumChildren, + /* [out] */ unsigned int __RPC_FAR* aUniqueID, + /* [out][retval] */ unsigned short __RPC_FAR* aNodeType); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributes( + /* [in] */ unsigned short aMaxAttribs, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aAttribNames, + /* [length_is][size_is][out] */ short __RPC_FAR* aNameSpaceIDs, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aAttribValues, + /* [out][retval] */ unsigned short __RPC_FAR* aNumAttribs); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributesForNames( + /* [in] */ unsigned short aMaxAttribs, + /* [length_is][size_is][in] */ BSTR __RPC_FAR* aAttribNames, + /* [length_is][size_is][in] */ short __RPC_FAR* aNameSpaceID, + /* [length_is][size_is][retval] */ BSTR __RPC_FAR* aAttribValues); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyle( + /* [in] */ unsigned short aMaxStyleProperties, + /* [in] */ boolean aUseAlternateView, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aStyleProperties, + /* [length_is][size_is][out] */ BSTR __RPC_FAR* aStyleValues, + /* [out][retval] */ unsigned short __RPC_FAR* aNumStyleProperties); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE + get_computedStyleForProperties( + /* [in] */ unsigned short aNumStyleProperties, + /* [in] */ boolean aUseAlternateView, + /* [length_is][size_is][in] */ BSTR __RPC_FAR* aStyleProperties, + /* [length_is][size_is][out][retval] */ BSTR __RPC_FAR* aStyleValues); + + virtual HRESULT STDMETHODCALLTYPE scrollTo(/* [in] */ boolean aScrollTopLeft); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_parentNode( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_firstChild( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_lastChild( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_previousSibling( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nextSibling( + /* [out][retval] */ ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_childAt( + /* [in] */ unsigned aChildIndex, + /* [out][retval] */ ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_innerHTML( + /* [out][retval] */ BSTR __RPC_FAR* aInnerHTML); + + virtual /* [local][propget] */ HRESULT STDMETHODCALLTYPE get_localInterface( + /* [retval][out] */ void __RPC_FAR* __RPC_FAR* aLocalInterface); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_language( + /* [out][retval] */ BSTR __RPC_FAR* aLanguage); + + private: + nsCOMPtr<nsINode> mNode; + RefPtr<AccessibleWrap> mWrap; + Maybe<uint32_t> mUniqueId; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_sdnAccessible_h_ diff --git a/accessible/windows/sdn/sdnDocAccessible.cpp b/accessible/windows/sdn/sdnDocAccessible.cpp new file mode 100644 index 0000000000..17d93ee766 --- /dev/null +++ b/accessible/windows/sdn/sdnDocAccessible.cpp @@ -0,0 +1,113 @@ +/* -*- 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 "sdnDocAccessible.h" + +#include "Accessible-inl.h" +#include "ISimpleDOM.h" + +#include "nsNameSpaceManager.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// sdnDocAccessible +//////////////////////////////////////////////////////////////////////////////// + +IMPL_IUNKNOWN_QUERY_HEAD(sdnDocAccessible) +IMPL_IUNKNOWN_QUERY_IFACE(ISimpleDOMDocument) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible) + +STDMETHODIMP +sdnDocAccessible::get_URL(BSTR __RPC_FAR* aURL) { + if (!aURL) return E_INVALIDARG; + *aURL = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString URL; + mAccessible->URL(URL); + if (URL.IsEmpty()) return S_FALSE; + + *aURL = ::SysAllocStringLen(URL.get(), URL.Length()); + return *aURL ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +sdnDocAccessible::get_title(BSTR __RPC_FAR* aTitle) { + if (!aTitle) return E_INVALIDARG; + *aTitle = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString title; + mAccessible->Title(title); + *aTitle = ::SysAllocStringLen(title.get(), title.Length()); + return *aTitle ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +sdnDocAccessible::get_mimeType(BSTR __RPC_FAR* aMimeType) { + if (!aMimeType) return E_INVALIDARG; + *aMimeType = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString mimeType; + mAccessible->MimeType(mimeType); + if (mimeType.IsEmpty()) return S_FALSE; + + *aMimeType = ::SysAllocStringLen(mimeType.get(), mimeType.Length()); + return *aMimeType ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +sdnDocAccessible::get_docType(BSTR __RPC_FAR* aDocType) { + if (!aDocType) return E_INVALIDARG; + *aDocType = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString docType; + mAccessible->DocType(docType); + if (docType.IsEmpty()) return S_FALSE; + + *aDocType = ::SysAllocStringLen(docType.get(), docType.Length()); + return *aDocType ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +sdnDocAccessible::get_nameSpaceURIForID(short aNameSpaceID, + BSTR __RPC_FAR* aNameSpaceURI) { + if (!aNameSpaceURI) return E_INVALIDARG; + *aNameSpaceURI = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + if (aNameSpaceID < 0) return E_INVALIDARG; // -1 is kNameSpaceID_Unknown + + nsAutoString nameSpaceURI; + nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance(); + if (nameSpaceManager) + nameSpaceManager->GetNameSpaceURI(aNameSpaceID, nameSpaceURI); + + if (nameSpaceURI.IsEmpty()) return S_FALSE; + + *aNameSpaceURI = + ::SysAllocStringLen(nameSpaceURI.get(), nameSpaceURI.Length()); + + return *aNameSpaceURI ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +sdnDocAccessible::put_alternateViewMediaTypes( + BSTR __RPC_FAR* aCommaSeparatedMediaTypes) { + if (!aCommaSeparatedMediaTypes) return E_INVALIDARG; + *aCommaSeparatedMediaTypes = nullptr; + + return mAccessible->IsDefunct() ? CO_E_OBJNOTCONNECTED : E_NOTIMPL; +} diff --git a/accessible/windows/sdn/sdnDocAccessible.h b/accessible/windows/sdn/sdnDocAccessible.h new file mode 100644 index 0000000000..5b1f9d6dd8 --- /dev/null +++ b/accessible/windows/sdn/sdnDocAccessible.h @@ -0,0 +1,53 @@ +/* -*- 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_sdnDocAccessible_h_ +#define mozilla_a11y_sdnDocAccessible_h_ + +#include "ISimpleDOM.h" +#include "IUnknownImpl.h" + +#include "DocAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +class sdnDocAccessible final : public ISimpleDOMDocument { + public: + explicit sdnDocAccessible(DocAccessibleWrap* aAccessible) + : mAccessible(aAccessible){}; + ~sdnDocAccessible(){}; + + DECL_IUNKNOWN + + // ISimpleDOMDocument + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_URL( + /* [out] */ BSTR __RPC_FAR* url); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_title( + /* [out] */ BSTR __RPC_FAR* title); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_mimeType( + /* [out] */ BSTR __RPC_FAR* mimeType); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_docType( + /* [out] */ BSTR __RPC_FAR* docType); + + virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nameSpaceURIForID( + /* [in] */ short nameSpaceID, + /* [out] */ BSTR __RPC_FAR* nameSpaceURI); + + virtual /* [id] */ HRESULT STDMETHODCALLTYPE put_alternateViewMediaTypes( + /* [in] */ BSTR __RPC_FAR* commaSeparatedMediaTypes); + + protected: + RefPtr<DocAccessibleWrap> mAccessible; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/sdn/sdnTextAccessible.cpp b/accessible/windows/sdn/sdnTextAccessible.cpp new file mode 100644 index 0000000000..0acffa1661 --- /dev/null +++ b/accessible/windows/sdn/sdnTextAccessible.cpp @@ -0,0 +1,165 @@ +/* -*- 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 "sdnTextAccessible.h" + +#include "ISimpleDOM.h" + +#include "nsCoreUtils.h" +#include "DocAccessible.h" + +#include "nsIFrame.h" +#include "nsFontMetrics.h" +#include "nsPresContext.h" +#include "nsLayoutUtils.h" +#include "nsRange.h" +#include "gfxTextRun.h" +#include "nsIAccessibleTypes.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// sdnTextAccessible +//////////////////////////////////////////////////////////////////////////////// + +IMPL_IUNKNOWN_QUERY_HEAD(sdnTextAccessible) +IMPL_IUNKNOWN_QUERY_IFACE(ISimpleDOMText) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible) + +STDMETHODIMP +sdnTextAccessible::get_domText(BSTR __RPC_FAR* aText) { + if (!aText) return E_INVALIDARG; + *aText = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsAutoString nodeValue; + + mAccessible->GetContent()->GetNodeValue(nodeValue); + if (nodeValue.IsEmpty()) return S_FALSE; + + *aText = ::SysAllocStringLen(nodeValue.get(), nodeValue.Length()); + return *aText ? S_OK : E_OUTOFMEMORY; +} + +STDMETHODIMP +sdnTextAccessible::get_clippedSubstringBounds( + unsigned int aStartIndex, unsigned int aEndIndex, int __RPC_FAR* aX, + int __RPC_FAR* aY, int __RPC_FAR* aWidth, int __RPC_FAR* aHeight) { + nscoord x = 0, y = 0, width = 0, height = 0; + HRESULT rv = get_unclippedSubstringBounds(aStartIndex, aEndIndex, &x, &y, + &width, &height); + if (FAILED(rv)) return rv; + + DocAccessible* document = mAccessible->Document(); + NS_ASSERTION( + document, + "There must always be a doc accessible, but there isn't. Crash!"); + + nsIntRect docRect = document->Bounds(); + nsIntRect unclippedRect(x, y, width, height); + + nsIntRect clippedRect; + clippedRect.IntersectRect(unclippedRect, docRect); + + *aX = clippedRect.X(); + *aY = clippedRect.Y(); + *aWidth = clippedRect.Width(); + *aHeight = clippedRect.Height(); + return S_OK; +} + +STDMETHODIMP +sdnTextAccessible::get_unclippedSubstringBounds( + unsigned int aStartIndex, unsigned int aEndIndex, int __RPC_FAR* aX, + int __RPC_FAR* aY, int __RPC_FAR* aWidth, int __RPC_FAR* aHeight) { + if (!aX || !aY || !aWidth || !aHeight) return E_INVALIDARG; + *aX = *aY = *aWidth = *aHeight = 0; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsIFrame* frame = mAccessible->GetFrame(); + NS_ENSURE_TRUE(frame, E_FAIL); + + nsPoint startPoint, endPoint; + nsIFrame* startFrame = + GetPointFromOffset(frame, aStartIndex, true, startPoint); + nsIFrame* endFrame = GetPointFromOffset(frame, aEndIndex, false, endPoint); + if (!startFrame || !endFrame) return E_FAIL; + + nsRect sum; + nsIFrame* iter = startFrame; + nsIFrame* stopLoopFrame = endFrame->GetNextContinuation(); + for (; iter != stopLoopFrame; iter = iter->GetNextContinuation()) { + nsRect rect = iter->GetScreenRectInAppUnits(); + nscoord start = (iter == startFrame) ? startPoint.x : 0; + nscoord end = (iter == endFrame) ? endPoint.x : rect.Width(); + rect.MoveByX(start); + rect.SetWidth(end - start); + sum.UnionRect(sum, rect); + } + + nsPresContext* presContext = mAccessible->Document()->PresContext(); + *aX = presContext->AppUnitsToDevPixels(sum.X()); + *aY = presContext->AppUnitsToDevPixels(sum.Y()); + *aWidth = presContext->AppUnitsToDevPixels(sum.Width()); + *aHeight = presContext->AppUnitsToDevPixels(sum.Height()); + + return S_OK; +} + +STDMETHODIMP +sdnTextAccessible::scrollToSubstring(unsigned int aStartIndex, + unsigned int aEndIndex) { + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + RefPtr<nsRange> range = nsRange::Create(mAccessible->GetContent()); + if (NS_FAILED(range->SetStart(mAccessible->GetContent(), aStartIndex))) + return E_FAIL; + + if (NS_FAILED(range->SetEnd(mAccessible->GetContent(), aEndIndex))) + return E_FAIL; + + nsresult rv = nsCoreUtils::ScrollSubstringTo( + mAccessible->GetFrame(), range, + nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE); + return GetHRESULT(rv); +} + +STDMETHODIMP +sdnTextAccessible::get_fontFamily(BSTR __RPC_FAR* aFontFamily) { + if (!aFontFamily) return E_INVALIDARG; + *aFontFamily = nullptr; + + if (mAccessible->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + nsIFrame* frame = mAccessible->GetFrame(); + if (!frame) return E_FAIL; + + RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(frame, 1.0f); + + const nsCString& name = + fm->GetThebesFontGroup()->GetFirstValidFont()->GetName(); + if (name.IsEmpty()) return S_FALSE; + + NS_ConvertUTF8toUTF16 str(name); + *aFontFamily = ::SysAllocStringLen(str.get(), str.Length()); + return *aFontFamily ? S_OK : E_OUTOFMEMORY; +} + +nsIFrame* sdnTextAccessible::GetPointFromOffset(nsIFrame* aContainingFrame, + int32_t aOffset, + bool aPreferNext, + nsPoint& aOutPoint) { + nsIFrame* textFrame = nullptr; + int32_t outOffset; + aContainingFrame->GetChildFrameContainingOffset(aOffset, aPreferNext, + &outOffset, &textFrame); + if (textFrame) textFrame->GetPointFromOffset(aOffset, &aOutPoint); + + return textFrame; +} diff --git a/accessible/windows/sdn/sdnTextAccessible.h b/accessible/windows/sdn/sdnTextAccessible.h new file mode 100644 index 0000000000..0a5bd909f5 --- /dev/null +++ b/accessible/windows/sdn/sdnTextAccessible.h @@ -0,0 +1,70 @@ +/* -*- 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_sdnTextAccessible_h_ +#define mozilla_a11y_sdnTextAccessible_h_ + +#include "ISimpleDOM.h" +#include "IUnknownImpl.h" + +#include "AccessibleWrap.h" + +class nsIFrame; +struct nsPoint; + +namespace mozilla { +namespace a11y { + +class sdnTextAccessible final : public ISimpleDOMText { + public: + explicit sdnTextAccessible(AccessibleWrap* aAccessible) + : mAccessible(aAccessible){}; + ~sdnTextAccessible() {} + + DECL_IUNKNOWN + + // ISimpleDOMText + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_domText( + /* [retval][out] */ BSTR __RPC_FAR* aText); + + virtual HRESULT STDMETHODCALLTYPE get_clippedSubstringBounds( + /* [in] */ unsigned int startIndex, + /* [in] */ unsigned int endIndex, + /* [out] */ int __RPC_FAR* aX, + /* [out] */ int __RPC_FAR* aY, + /* [out] */ int __RPC_FAR* aWidth, + /* [out] */ int __RPC_FAR* aHeight); + + virtual HRESULT STDMETHODCALLTYPE get_unclippedSubstringBounds( + /* [in] */ unsigned int aStartIndex, + /* [in] */ unsigned int aEndIndex, + /* [out] */ int __RPC_FAR* aX, + /* [out] */ int __RPC_FAR* aY, + /* [out] */ int __RPC_FAR* aWidth, + /* [out] */ int __RPC_FAR* aHeight); + + virtual HRESULT STDMETHODCALLTYPE scrollToSubstring( + /* [in] */ unsigned int aStartIndex, + /* [in] */ unsigned int aEndIndex); + + virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_fontFamily( + /* [retval][out] */ BSTR __RPC_FAR* aFontFamily); + + private: + /** + * Return child frame containing offset on success. + */ + nsIFrame* GetPointFromOffset(nsIFrame* aContainingFrame, int32_t aOffset, + bool aPreferNext, nsPoint& aOutPoint); + + RefPtr<AccessibleWrap> mAccessible; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/windows/uia/moz.build b/accessible/windows/uia/moz.build new file mode 100644 index 0000000000..058aacc579 --- /dev/null +++ b/accessible/windows/uia/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "uiaRawElmProvider.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/windows/msaa", + "/accessible/xpcom", + "/accessible/xul", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp new file mode 100644 index 0000000000..a0634c446c --- /dev/null +++ b/accessible/windows/uia/uiaRawElmProvider.cpp @@ -0,0 +1,197 @@ +/* -*- 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 "uiaRawElmProvider.h" + +#include "Accessible-inl.h" +#include "AccessibleWrap.h" +#include "ARIAMap.h" +#include "nsIPersistentProperties2.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// uiaRawElmProvider +//////////////////////////////////////////////////////////////////////////////// + +IMPL_IUNKNOWN2(uiaRawElmProvider, IAccessibleEx, IRawElementProviderSimple) + +//////////////////////////////////////////////////////////////////////////////// +// IAccessibleEx + +STDMETHODIMP +uiaRawElmProvider::GetObjectForChild( + long aIdChild, __RPC__deref_out_opt IAccessibleEx** aAccEx) { + if (!aAccEx) return E_INVALIDARG; + + *aAccEx = nullptr; + + return mAcc->IsDefunct() ? CO_E_OBJNOTCONNECTED : S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::GetIAccessiblePair(__RPC__deref_out_opt IAccessible** aAcc, + __RPC__out long* aIdChild) { + if (!aAcc || !aIdChild) return E_INVALIDARG; + + *aAcc = nullptr; + *aIdChild = 0; + + if (mAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + *aIdChild = CHILDID_SELF; + RefPtr<AccessibleWrap> copy(mAcc); + copy.forget(aAcc); + + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::GetRuntimeId(__RPC__deref_out_opt SAFEARRAY** aRuntimeIds) { + if (!aRuntimeIds) return E_INVALIDARG; + + int ids[] = {UiaAppendRuntimeId, + static_cast<int>(reinterpret_cast<intptr_t>(mAcc->UniqueID()))}; + *aRuntimeIds = SafeArrayCreateVector(VT_I4, 0, 2); + if (!*aRuntimeIds) return E_OUTOFMEMORY; + + for (LONG i = 0; i < (LONG)ArrayLength(ids); i++) + SafeArrayPutElement(*aRuntimeIds, &i, (void*)&(ids[i])); + + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::ConvertReturnedElement( + __RPC__in_opt IRawElementProviderSimple* aRawElmProvider, + __RPC__deref_out_opt IAccessibleEx** aAccEx) { + if (!aRawElmProvider || !aAccEx) return E_INVALIDARG; + + *aAccEx = nullptr; + + void* instancePtr = nullptr; + HRESULT hr = aRawElmProvider->QueryInterface(IID_IAccessibleEx, &instancePtr); + if (SUCCEEDED(hr)) *aAccEx = static_cast<IAccessibleEx*>(instancePtr); + + return hr; +} + +//////////////////////////////////////////////////////////////////////////////// +// IRawElementProviderSimple + +STDMETHODIMP +uiaRawElmProvider::get_ProviderOptions( + __RPC__out enum ProviderOptions* aOptions) { + if (!aOptions) return E_INVALIDARG; + + // This method is not used with IAccessibleEx implementations. + *aOptions = ProviderOptions_ServerSideProvider; + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::GetPatternProvider( + PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) { + if (!aPatternProvider) return E_INVALIDARG; + + *aPatternProvider = nullptr; + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, + __RPC__out VARIANT* aPropertyValue) { + if (!aPropertyValue) return E_INVALIDARG; + + if (mAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + + aPropertyValue->vt = VT_EMPTY; + + switch (aPropertyId) { + // Accelerator Key / shortcut. + case UIA_AcceleratorKeyPropertyId: { + nsAutoString keyString; + + mAcc->KeyboardShortcut().ToString(keyString); + + if (!keyString.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(keyString.get()); + return S_OK; + } + + break; + } + + // Access Key / mneumonic. + case UIA_AccessKeyPropertyId: { + nsAutoString keyString; + + mAcc->AccessKey().ToString(keyString); + + if (!keyString.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(keyString.get()); + return S_OK; + } + + break; + } + + // ARIA Role / shortcut + case UIA_AriaRolePropertyId: { + nsAutoString xmlRoles; + + nsCOMPtr<nsIPersistentProperties> attributes = mAcc->Attributes(); + attributes->GetStringProperty("xml-roles"_ns, xmlRoles); + + if (!xmlRoles.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(xmlRoles.get()); + return S_OK; + } + + break; + } + + // ARIA Properties + case UIA_AriaPropertiesPropertyId: { + nsAutoString ariaProperties; + + aria::AttrIterator attribIter(mAcc->GetContent()); + nsAutoString attribName, attribValue; + while (attribIter.Next(attribName, attribValue)) { + ariaProperties.Append(attribName); + ariaProperties.Append('='); + ariaProperties.Append(attribValue); + ariaProperties.Append(';'); + } + + if (!ariaProperties.IsEmpty()) { + // remove last delimiter: + ariaProperties.Truncate(ariaProperties.Length() - 1); + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(ariaProperties.get()); + return S_OK; + } + + break; + } + } + + return S_OK; +} + +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; + return S_OK; +} diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h new file mode 100644 index 0000000000..4a4aecdbe2 --- /dev/null +++ b/accessible/windows/uia/uiaRawElmProvider.h @@ -0,0 +1,75 @@ +/* -*- 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_uiaRawElmProvider_h__ +#define mozilla_a11y_uiaRawElmProvider_h__ + +#include "objbase.h" +#include "AccessibleWrap.h" +#include "IUnknownImpl.h" +#include "uiautomation.h" + +namespace mozilla { +namespace a11y { + +class AccessibleWrap; + +/** + * IRawElementProviderSimple implementation (maintains IAccessibleEx approach). + */ +class uiaRawElmProvider final : public IAccessibleEx, + public IRawElementProviderSimple { + public: + explicit uiaRawElmProvider(AccessibleWrap* aAcc) : mAcc(aAcc) {} + + // IUnknown + DECL_IUNKNOWN + + // IAccessibleEx + virtual HRESULT STDMETHODCALLTYPE GetObjectForChild( + /* [in] */ long aIdChild, + /* [retval][out] */ __RPC__deref_out_opt IAccessibleEx** aAccEx); + + virtual HRESULT STDMETHODCALLTYPE GetIAccessiblePair( + /* [out] */ __RPC__deref_out_opt IAccessible** aAcc, + /* [out] */ __RPC__out long* aIdChild); + + virtual HRESULT STDMETHODCALLTYPE GetRuntimeId( + /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRuntimeIds); + + virtual HRESULT STDMETHODCALLTYPE ConvertReturnedElement( + /* [in] */ __RPC__in_opt IRawElementProviderSimple* aRawElmProvider, + /* [out] */ __RPC__deref_out_opt IAccessibleEx** aAccEx); + + // 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); + + private: + uiaRawElmProvider() = delete; + uiaRawElmProvider& operator=(const uiaRawElmProvider&) = delete; + uiaRawElmProvider(const uiaRawElmProvider&) = delete; + + protected: + RefPtr<AccessibleWrap> mAcc; +}; + +} // namespace a11y +} // namespace mozilla + +#endif |