summaryrefslogtreecommitdiffstats
path: root/accessible/windows
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /accessible/windows
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--accessible/windows/ia2/ia2Accessible.cpp615
-rw-r--r--accessible/windows/ia2/ia2Accessible.h125
-rw-r--r--accessible/windows/ia2/ia2AccessibleAction.cpp152
-rw-r--r--accessible/windows/ia2/ia2AccessibleAction.h84
-rw-r--r--accessible/windows/ia2/ia2AccessibleApplication.cpp94
-rw-r--r--accessible/windows/ia2/ia2AccessibleApplication.h49
-rw-r--r--accessible/windows/ia2/ia2AccessibleComponent.cpp106
-rw-r--r--accessible/windows/ia2/ia2AccessibleComponent.h40
-rw-r--r--accessible/windows/ia2/ia2AccessibleEditableText.cpp107
-rw-r--r--accessible/windows/ia2/ia2AccessibleEditableText.h59
-rw-r--r--accessible/windows/ia2/ia2AccessibleHyperlink.cpp166
-rw-r--r--accessible/windows/ia2/ia2AccessibleHyperlink.h55
-rw-r--r--accessible/windows/ia2/ia2AccessibleHypertext.cpp138
-rw-r--r--accessible/windows/ia2/ia2AccessibleHypertext.h68
-rw-r--r--accessible/windows/ia2/ia2AccessibleImage.cpp81
-rw-r--r--accessible/windows/ia2/ia2AccessibleImage.h51
-rw-r--r--accessible/windows/ia2/ia2AccessibleRelation.cpp94
-rw-r--r--accessible/windows/ia2/ia2AccessibleRelation.h78
-rw-r--r--accessible/windows/ia2/ia2AccessibleTable.cpp570
-rw-r--r--accessible/windows/ia2/ia2AccessibleTable.h178
-rw-r--r--accessible/windows/ia2/ia2AccessibleTableCell.cpp186
-rw-r--r--accessible/windows/ia2/ia2AccessibleTableCell.h71
-rw-r--r--accessible/windows/ia2/ia2AccessibleText.cpp481
-rw-r--r--accessible/windows/ia2/ia2AccessibleText.h255
-rw-r--r--accessible/windows/ia2/ia2AccessibleValue.cpp125
-rw-r--r--accessible/windows/ia2/ia2AccessibleValue.h43
-rw-r--r--accessible/windows/ia2/moz.build61
-rw-r--r--accessible/windows/moz.build7
-rw-r--r--accessible/windows/msaa/AccessibleWrap.cpp289
-rw-r--r--accessible/windows/msaa/AccessibleWrap.h109
-rw-r--r--accessible/windows/msaa/ApplicationAccessibleWrap.cpp43
-rw-r--r--accessible/windows/msaa/ApplicationAccessibleWrap.h31
-rw-r--r--accessible/windows/msaa/Compatibility.cpp283
-rw-r--r--accessible/windows/msaa/Compatibility.h125
-rw-r--r--accessible/windows/msaa/CompatibilityUIA.cpp339
-rw-r--r--accessible/windows/msaa/DocAccessibleWrap.cpp106
-rw-r--r--accessible/windows/msaa/DocAccessibleWrap.h39
-rw-r--r--accessible/windows/msaa/EnumVariant.cpp90
-rw-r--r--accessible/windows/msaa/EnumVariant.h62
-rw-r--r--accessible/windows/msaa/GeckoCustom.cpp95
-rw-r--r--accessible/windows/msaa/GeckoCustom.h52
-rw-r--r--accessible/windows/msaa/HyperTextAccessibleWrap.cpp37
-rw-r--r--accessible/windows/msaa/HyperTextAccessibleWrap.h34
-rw-r--r--accessible/windows/msaa/IUnknownImpl.cpp36
-rw-r--r--accessible/windows/msaa/IUnknownImpl.h173
-rw-r--r--accessible/windows/msaa/LazyInstantiator.cpp755
-rw-r--r--accessible/windows/msaa/LazyInstantiator.h130
-rw-r--r--accessible/windows/msaa/MsaaAccessible.cpp1793
-rw-r--r--accessible/windows/msaa/MsaaAccessible.h227
-rw-r--r--accessible/windows/msaa/MsaaDocAccessible.cpp173
-rw-r--r--accessible/windows/msaa/MsaaDocAccessible.h70
-rw-r--r--accessible/windows/msaa/MsaaIdGenerator.cpp296
-rw-r--r--accessible/windows/msaa/MsaaIdGenerator.h68
-rw-r--r--accessible/windows/msaa/MsaaRootAccessible.cpp113
-rw-r--r--accessible/windows/msaa/MsaaRootAccessible.h55
-rw-r--r--accessible/windows/msaa/MsaaXULMenuAccessible.cpp78
-rw-r--r--accessible/windows/msaa/MsaaXULMenuAccessible.h30
-rw-r--r--accessible/windows/msaa/NtUndoc.h85
-rw-r--r--accessible/windows/msaa/Platform.cpp407
-rw-r--r--accessible/windows/msaa/RootAccessibleWrap.cpp44
-rw-r--r--accessible/windows/msaa/RootAccessibleWrap.h33
-rw-r--r--accessible/windows/msaa/ServiceProvider.cpp128
-rw-r--r--accessible/windows/msaa/ServiceProvider.h37
-rw-r--r--accessible/windows/msaa/moz.build80
-rw-r--r--accessible/windows/msaa/nsEventMap.h110
-rw-r--r--accessible/windows/msaa/nsWinUtils.cpp172
-rw-r--r--accessible/windows/msaa/nsWinUtils.h105
-rw-r--r--accessible/windows/sdn/moz.build26
-rw-r--r--accessible/windows/sdn/sdnAccessible-inl.h49
-rw-r--r--accessible/windows/sdn/sdnAccessible.cpp522
-rw-r--r--accessible/windows/sdn/sdnAccessible.h149
-rw-r--r--accessible/windows/sdn/sdnDocAccessible.cpp117
-rw-r--r--accessible/windows/sdn/sdnDocAccessible.h52
-rw-r--r--accessible/windows/sdn/sdnTextAccessible.cpp166
-rw-r--r--accessible/windows/sdn/sdnTextAccessible.h69
-rw-r--r--accessible/windows/uia/moz.build22
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.cpp206
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.h75
78 files changed, 12524 insertions, 0 deletions
diff --git a/accessible/windows/ia2/ia2Accessible.cpp b/accessible/windows/ia2/ia2Accessible.cpp
new file mode 100644
index 0000000000..3954d4c307
--- /dev/null
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -0,0 +1,615 @@
+/* -*- 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 "AccAttributes.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 "nsISimpleEnumerator.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// 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;
+}
+
+AccessibleWrap* ia2Accessible::LocalAcc() {
+ return static_cast<MsaaAccessible*>(this)->LocalAcc();
+}
+
+Accessible* ia2Accessible::Acc() {
+ return static_cast<MsaaAccessible*>(this)->Acc();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessible2
+
+STDMETHODIMP
+ia2Accessible::get_nRelations(long* aNRelations) {
+ if (!aNRelations) return E_INVALIDARG;
+ *aNRelations = 0;
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ 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;
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ 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) {
+ MsaaAccessible* msaa = static_cast<MsaaAccessible*>(this);
+ msaa->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;
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ 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()) {
+ MsaaAccessible* msaa = static_cast<MsaaAccessible*>(this);
+ msaa->AssociateCOMObjectForDisconnection(ia2Rel);
+ ia2Rel.forget(aRelation + (*aNRelations));
+ (*aNRelations)++;
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2Accessible::role(long* aRole) {
+ if (!aRole) return E_INVALIDARG;
+ *aRole = 0;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+#define ROLE(_geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
+ ia2Role, androidClass, nameRule) \
+ case roles::_geckoRole: \
+ *aRole = ia2Role; \
+ break;
+
+ a11y::role geckoRole;
+ 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.
+ 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) {
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ acc->ScrollTo(aScrollType);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2Accessible::scrollToPoint(enum IA2CoordinateType aCoordType, long aX,
+ long aY) {
+ if (!Acc()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AccessibleWrap* acc = LocalAcc();
+ if (!acc) {
+ return E_NOTIMPL; // XXX Not supported for RemoteAccessible yet.
+ }
+
+ uint32_t geckoCoordType =
+ (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE)
+ ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
+ : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+
+ 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;
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ 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.
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ *aStates = IA2_STATE_DEFUNCT;
+ return S_OK;
+ }
+
+ uint64_t state;
+ 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;
+
+ Accessible* acc = Acc();
+ *aUniqueID = MsaaAccessible::GetChildIDFor(acc);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2Accessible::get_windowHandle(HWND* aWindowHandle) {
+ if (!aWindowHandle) return E_INVALIDARG;
+ *aWindowHandle = 0;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ *aWindowHandle = MsaaAccessible::GetHWNDFor(acc);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2Accessible::get_indexInParent(long* aIndexInParent) {
+ if (!aIndexInParent) return E_INVALIDARG;
+ *aIndexInParent = -1;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ *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.
+
+ if (!Acc()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AccessibleWrap* acc = LocalAcc();
+ if (!acc) {
+ return E_NOTIMPL; // XXX Not supported for RemoteAccessible yet.
+ }
+
+ 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;
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ // The format is name:value;name:value; with \ for escaping these
+ // characters ":;=,\".
+ RefPtr<AccAttributes> attributes = acc->Attributes();
+ return ConvertToIA2Attributes(attributes, aAttributes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+ if (!Acc()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AccessibleWrap* acc = LocalAcc();
+ if (!acc) {
+ return E_NOTIMPL; // XXX Not supported for RemoteAccessible yet.
+ }
+
+ int32_t caretOffset = -1;
+ LocalAccessible* accWithCaret =
+ SelectionMgr()->AccessibleWithCaret(&caretOffset);
+ if (!accWithCaret || acc->Document() != accWithCaret->Document())
+ return S_FALSE;
+
+ LocalAccessible* child = accWithCaret;
+ while (!child->IsDoc() && child != acc) child = child->LocalParent();
+
+ if (child != acc) return S_FALSE;
+
+ RefPtr<IAccessible2> ia2WithCaret;
+ accWithCaret->GetNativeInterface(getter_AddRefs(ia2WithCaret));
+ ia2WithCaret.forget(aAccessible);
+ *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;
+
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ nsTArray<Accessible*> targets;
+ 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++) {
+ (*aTargets)[i] = MsaaAccessible::NativeAccessible(targets[i]);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2Accessible::get_selectionRanges(IA2Range** aRanges, long* aNRanges) {
+ if (!aRanges || !aNRanges) return E_INVALIDARG;
+
+ *aNRanges = 0;
+
+ if (!Acc()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AccessibleWrap* acc = LocalAcc();
+ if (!acc) {
+ return E_NOTIMPL; // XXX Not supported for RemoteAccessible yet.
+ }
+
+ 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++) {
+ RefPtr<IAccessible2> anchor =
+ MsaaAccessible::GetFrom(ranges[idx].StartContainer());
+ anchor.forget(&(*aRanges)[idx].anchor);
+
+ (*aRanges)[idx].anchorOffset = ranges[idx].StartOffset();
+
+ RefPtr<IAccessible2> active =
+ MsaaAccessible::GetFrom(ranges[idx].EndContainer());
+ active.forget(&(*aRanges)[idx].active);
+
+ (*aRanges)[idx].activeOffset = ranges[idx].EndOffset();
+ }
+
+ return S_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Helpers
+
+static inline void EscapeAttributeChars(nsString& aStr) {
+ int32_t offset = 0;
+ static const char16_t kCharsToEscape[] = u":;=,\\";
+ while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
+ aStr.Insert('\\', offset);
+ offset += 2;
+ }
+}
+
+HRESULT
+ia2Accessible::ConvertToIA2Attributes(AccAttributes* aAttributes,
+ BSTR* aIA2Attributes) {
+ *aIA2Attributes = nullptr;
+
+ // The format is name:value;name:value; with \ for escaping these
+ // characters ":;=,\".
+
+ if (!aAttributes) return S_FALSE;
+
+ nsAutoString strAttrs;
+
+ for (auto iter : *aAttributes) {
+ nsAutoString name;
+ iter.NameAsString(name);
+ EscapeAttributeChars(name);
+
+ nsAutoString value;
+ iter.ValueAsString(value);
+ EscapeAttributeChars(value);
+
+ strAttrs.Append(name);
+ 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..0015a45c23
--- /dev/null
+++ b/accessible/windows/ia2/ia2Accessible.h
@@ -0,0 +1,125 @@
+/* -*- 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 "nsTArray.h"
+
+#include "Accessible2_3.h"
+
+namespace mozilla {
+namespace a11y {
+class Accessible;
+class AccAttributes;
+class AccessibleWrap;
+
+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(AccAttributes* aAttributes,
+ BSTR* aIA2Attributes);
+
+ private:
+ AccessibleWrap* LocalAcc();
+ Accessible* Acc();
+};
+
+} // 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..d89bd79ce1
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleAction.cpp
@@ -0,0 +1,152 @@
+/* -*- 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;
+
+Accessible* ia2AccessibleAction::Acc() {
+ return static_cast<MsaaAccessible*>(this)->Acc();
+}
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleAction::QueryInterface(REFIID iid, void** ppv) {
+ if (!ppv) return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleAction == iid) {
+ *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;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ *aActionCount = acc->ActionCount();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleAction::doAction(long aActionIndex) {
+ Accessible* acc = Acc();
+ if (!acc) 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;
+
+ Accessible* acc = Acc();
+ if (!acc) 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;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ // Expose KeyboardShortcut if it's not exposed via MSAA accKeyboardShortcut.
+ LocalAccessible* localAcc = acc->AsLocal();
+ if (!localAcc) {
+ // RemoteAccessibles can't have a KeyboardShortcut.
+ return S_FALSE;
+ }
+
+ KeyBinding keyBinding = acc->AccessKey();
+ if (keyBinding.IsEmpty()) {
+ // In this case, KeyboardShortcut will be exposed via MSAA
+ // accKeyboardShortcut.
+ return S_FALSE;
+ }
+
+ // MSAA accKeyboardShortcut will expose AccessKey.
+ keyBinding = localAcc->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;
+
+ Accessible* acc = Acc();
+ if (!acc) 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..99f259083a
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleAction.h
@@ -0,0 +1,84 @@
+/* -*- 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);
+
+ private:
+ Accessible* Acc();
+};
+
+} // 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/ia2AccessibleApplication.cpp b/accessible/windows/ia2/ia2AccessibleApplication.cpp
new file mode 100644
index 0000000000..7844e97074
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleApplication.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "ia2AccessibleApplication.h"
+
+#include "AccessibleApplication_i.c"
+#include "ApplicationAccessibleWrap.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+ApplicationAccessible* ia2AccessibleApplication::AppAcc() {
+ return static_cast<ApplicationAccessible*>(LocalAcc());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IUnknown
+
+IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleApplication)
+IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleApplication)
+IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(MsaaAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleApplication
+
+STDMETHODIMP
+ia2AccessibleApplication::get_appName(BSTR* aName) {
+ if (!aName) return E_INVALIDARG;
+
+ *aName = nullptr;
+
+ ApplicationAccessible* appAcc = AppAcc();
+ if (!appAcc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString name;
+ appAcc->AppName(name);
+ if (name.IsEmpty()) return S_FALSE;
+
+ *aName = ::SysAllocStringLen(name.get(), name.Length());
+ return *aName ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+ia2AccessibleApplication::get_appVersion(BSTR* aVersion) {
+ if (!aVersion) return E_INVALIDARG;
+
+ *aVersion = nullptr;
+
+ ApplicationAccessible* appAcc = AppAcc();
+ if (!appAcc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString version;
+ appAcc->AppVersion(version);
+ if (version.IsEmpty()) return S_FALSE;
+
+ *aVersion = ::SysAllocStringLen(version.get(), version.Length());
+ return *aVersion ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+ia2AccessibleApplication::get_toolkitName(BSTR* aName) {
+ if (!aName) return E_INVALIDARG;
+
+ ApplicationAccessible* appAcc = AppAcc();
+ if (!appAcc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString name;
+ appAcc->PlatformName(name);
+ if (name.IsEmpty()) return S_FALSE;
+
+ *aName = ::SysAllocStringLen(name.get(), name.Length());
+ return *aName ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+ia2AccessibleApplication::get_toolkitVersion(BSTR* aVersion) {
+ if (!aVersion) return E_INVALIDARG;
+
+ *aVersion = nullptr;
+
+ ApplicationAccessible* appAcc = AppAcc();
+ if (!appAcc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString version;
+ appAcc->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/ia2/ia2AccessibleApplication.h b/accessible/windows/ia2/ia2AccessibleApplication.h
new file mode 100644
index 0000000000..283dc38471
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleApplication.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 IA2_ACCESSIBLE_APPLICATION_H_
+#define IA2_ACCESSIBLE_APPLICATION_H_
+
+#include "AccessibleApplication.h"
+#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+class ApplicationAccessible;
+
+class ia2AccessibleApplication : public IAccessibleApplication,
+ public MsaaAccessible {
+ public:
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+ IMPL_IUNKNOWN_REFCOUNTING_INHERITED(MsaaAccessible)
+
+ // 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);
+
+ protected:
+ using MsaaAccessible::MsaaAccessible;
+
+ private:
+ ApplicationAccessible* AppAcc();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleComponent.cpp b/accessible/windows/ia2/ia2AccessibleComponent.cpp
new file mode 100644
index 0000000000..9c22a66cad
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleComponent.cpp
@@ -0,0 +1,106 @@
+/* -*- 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;
+
+AccessibleWrap* ia2AccessibleComponent::LocalAcc() {
+ return static_cast<MsaaAccessible*>(this)->LocalAcc();
+}
+
+// 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 = LocalAcc();
+ if (!acc) 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;
+
+ LayoutDeviceIntRect 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->LocalParent()) {
+ *aX = rect.X();
+ *aY = rect.Y();
+ return S_OK;
+ }
+
+ // The coordinates of the bounding box are given relative to the parent's
+ // coordinate system.
+ LayoutDeviceIntRect parentRect = acc->LocalParent()->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 = LocalAcc();
+ if (!acc) 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 = LocalAcc();
+ if (!acc) 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..507bbbd628
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleComponent.h
@@ -0,0 +1,40 @@
+/* -*- 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 AccessibleWrap;
+
+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);
+
+ private:
+ AccessibleWrap* LocalAcc();
+};
+
+} // 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..586ac1f626
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleEditableText.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "ia2AccessibleHypertext.h"
+
+#include "AccessibleEditableText_i.c"
+#include "HyperTextAccessible-inl.h"
+#include "HyperTextAccessibleWrap.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using namespace mozilla::a11y;
+
+HyperTextAccessibleWrap* ia2AccessibleEditableText::TextAcc() {
+ auto hyp = static_cast<ia2AccessibleHypertext*>(this);
+ AccessibleWrap* acc = static_cast<MsaaAccessible*>(hyp)->LocalAcc();
+ return static_cast<HyperTextAccessibleWrap*>(acc);
+}
+
+// IAccessibleEditableText
+
+STDMETHODIMP
+ia2AccessibleEditableText::copyText(long aStartOffset, long aEndOffset) {
+ HyperTextAccessible* textAcc = TextAcc();
+ if (!textAcc) 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) {
+ HyperTextAccessible* textAcc = TextAcc();
+ if (!textAcc) 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);
+
+ HyperTextAccessible* textAcc = TextAcc();
+ if (!textAcc) 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) {
+ HyperTextAccessible* textAcc = TextAcc();
+ if (!textAcc) return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) return E_INVALIDARG;
+
+ textAcc->CutText(aStartOffset, aEndOffset);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::pasteText(long aOffset) {
+ RefPtr<HyperTextAccessible> textAcc = TextAcc();
+ if (!textAcc) 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 = TextAcc();
+ if (!textAcc) 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..11029c1759
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleEditableText.h
@@ -0,0 +1,59 @@
+/* -*- 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 HyperTextAccessibleWrap;
+
+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);
+
+ private:
+ HyperTextAccessibleWrap* TextAcc();
+};
+
+} // 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..6a49508da9
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp
@@ -0,0 +1,166 @@
+/* -*- 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;
+
+Accessible* ia2AccessibleHyperlink::Acc() {
+ return static_cast<MsaaAccessible*>(this)->Acc();
+}
+
+AccessibleWrap* ia2AccessibleHyperlink::LocalAcc() {
+ return static_cast<MsaaAccessible*>(this)->LocalAcc();
+}
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleHyperlink::QueryInterface(REFIID iid, void** ppv) {
+ if (!ppv) return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleHyperlink == iid) {
+ Accessible* acc = Acc();
+ if (!acc || !acc->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);
+
+ LocalAccessible* thisObj = LocalAcc();
+ if (!thisObj) {
+ 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;
+
+ RefPtr<IAccessible> result;
+ anchor->GetNativeInterface(getter_AddRefs(result));
+ result.forget(&aAnchor->punkVal);
+ aAnchor->vt = VT_UNKNOWN;
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleHyperlink::get_anchorTarget(long aIndex, VARIANT* aAnchorTarget) {
+ if (!aAnchorTarget) {
+ return E_INVALIDARG;
+ }
+
+ VariantInit(aAnchorTarget);
+
+ LocalAccessible* thisObj = LocalAcc();
+ if (!thisObj) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ nsAutoCString uriStr;
+
+ 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;
+
+ Accessible* thisObj = Acc();
+ if (!thisObj) {
+ 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;
+
+ Accessible* thisObj = Acc();
+ if (!thisObj) {
+ 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;
+
+ LocalAccessible* thisObj = LocalAcc();
+ if (!thisObj) {
+ 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..e7a2c5a0e2
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHyperlink.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_HYPERLINK_H
+#define _ACCESSIBLE_HYPERLINK_H
+
+#include "nsISupports.h"
+
+#include "ia2AccessibleAction.h"
+#include "AccessibleHyperlink.h"
+
+namespace mozilla {
+namespace a11y {
+class Accessible;
+class AccessibleWrap;
+
+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);
+
+ private:
+ Accessible* Acc();
+ AccessibleWrap* LocalAcc();
+};
+
+} // 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..220071c883
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHypertext.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 "ia2AccessibleHypertext.h"
+
+#include "AccessibleHypertext_i.c"
+
+#include "IUnknownImpl.h"
+
+using namespace mozilla::a11y;
+
+HyperTextAccessibleBase* ia2AccessibleHypertext::TextAcc() {
+ Accessible* acc = Acc();
+ return acc ? acc->AsHyperTextBase() : nullptr;
+}
+
+// IUnknown
+STDMETHODIMP
+ia2AccessibleHypertext::QueryInterface(REFIID aIID, void** aInstancePtr) {
+ if (!aInstancePtr) return E_FAIL;
+
+ *aInstancePtr = nullptr;
+
+ Accessible* acc = Acc();
+ if (acc && acc->IsTextRole()) {
+ bool isLocal = acc->IsLocal();
+ 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 && isLocal) {
+ *aInstancePtr = static_cast<IAccessibleHypertext2*>(this);
+ } else if (aIID == IID_IAccessibleEditableText && isLocal) {
+ *aInstancePtr = static_cast<IAccessibleEditableText*>(this);
+ }
+
+ if (*aInstancePtr) {
+ AddRef();
+ return S_OK;
+ }
+ }
+
+ return MsaaAccessible::QueryInterface(aIID, aInstancePtr);
+}
+
+// IAccessibleHypertext
+
+STDMETHODIMP
+ia2AccessibleHypertext::get_nHyperlinks(long* aHyperlinkCount) {
+ if (!aHyperlinkCount) return E_INVALIDARG;
+
+ *aHyperlinkCount = 0;
+
+ HyperTextAccessibleBase* hyperText = TextAcc();
+ if (!hyperText) 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;
+
+ HyperTextAccessibleBase* hyperText = TextAcc();
+ if (!hyperText) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ Accessible* hyperLink = hyperText->LinkAt(aLinkIndex);
+
+ if (!hyperLink) return E_FAIL;
+
+ RefPtr<IAccessibleHyperlink> result = MsaaAccessible::GetFrom(hyperLink);
+ result.forget(aHyperlink);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleHypertext::get_hyperlinkIndex(long aCharIndex,
+ long* aHyperlinkIndex) {
+ if (!aHyperlinkIndex) return E_INVALIDARG;
+
+ *aHyperlinkIndex = 0;
+
+ HyperTextAccessibleBase* hyperAcc = TextAcc();
+ if (!hyperAcc) 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;
+
+ HyperTextAccessibleBase* hyperText = TextAcc();
+ if (!hyperText) {
+ 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) {
+ Accessible* hyperLink = hyperText->LinkAt(i);
+ MOZ_ASSERT(hyperLink);
+ RefPtr<IAccessibleHyperlink> iaHyper = MsaaAccessible::GetFrom(hyperLink);
+ iaHyper.forget(&(*aHyperlinks)[i]);
+ }
+
+ return S_OK;
+}
diff --git a/accessible/windows/ia2/ia2AccessibleHypertext.h b/accessible/windows/ia2/ia2AccessibleHypertext.h
new file mode 100644
index 0000000000..2defbc8eeb
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHypertext.h
@@ -0,0 +1,68 @@
+/* -*- 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 "ia2AccessibleEditableText.h"
+#include "ia2AccessibleText.h"
+#include "AccessibleHypertext2.h"
+#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+class HyperTextAccessibleBase;
+
+class ia2AccessibleHypertext : public ia2AccessibleText,
+ public IAccessibleHypertext2,
+ public ia2AccessibleEditableText,
+ public MsaaAccessible {
+ public:
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+ IMPL_IUNKNOWN_REFCOUNTING_INHERITED(MsaaAccessible)
+
+ // IAccessible2
+ // We indirectly inherit IAccessible2, which has a get_attributes method,
+ // but IAccessibleText also has a get_attributes method with a different
+ // signature. We want both.
+ using MsaaAccessible::get_attributes;
+
+ // 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);
+
+ protected:
+ using MsaaAccessible::MsaaAccessible;
+
+ private:
+ HyperTextAccessibleBase* TextAcc();
+};
+
+} // 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..529269b566
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleImage.cpp
@@ -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/. */
+
+#include "ia2AccessibleImage.h"
+
+#include "AccessibleImage_i.c"
+
+#include "ImageAccessible.h"
+#include "IUnknownImpl.h"
+#include "nsIAccessibleTypes.h"
+
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// IUnknown
+IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleImage)
+IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleImage)
+IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(MsaaAccessible)
+
+// IAccessibleImage
+
+STDMETHODIMP
+ia2AccessibleImage::get_description(BSTR* aDescription) {
+ if (!aDescription) return E_INVALIDARG;
+
+ *aDescription = nullptr;
+
+ Accessible* acc = Acc();
+ if (!acc) 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;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ uint32_t geckoCoordType =
+ (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE)
+ ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
+ : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+
+ LayoutDeviceIntPoint pos = acc->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;
+
+ Accessible* acc = Acc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ LayoutDeviceIntSize size = acc->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..6302130b3a
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleImage.h
@@ -0,0 +1,51 @@
+/* -*- 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"
+#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+class ImageAccessible;
+
+class ia2AccessibleImage : public IAccessibleImage, public MsaaAccessible {
+ public:
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+ IMPL_IUNKNOWN_REFCOUNTING_INHERITED(MsaaAccessible)
+
+ // IAccessibleAction
+ // We indirectly inherit IAccessibleAction, which has a get_description
+ // method, but IAccessibleImage also has a get_description method with a
+ // different signature. We want both.
+ using MsaaAccessible::get_description;
+
+ // 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);
+
+ protected:
+ using MsaaAccessible::MsaaAccessible;
+};
+
+} // 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..007ca63aa6
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleRelation.cpp
@@ -0,0 +1,94 @@
+/* -*- 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(
+ already_AddRefed(MsaaAccessible::NativeAccessible(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;
+
+ RefPtr<IUnknown> target = mTargets[aTargetIndex];
+ target.forget(aTarget);
+
+ 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..63276b8e00
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleRelation.h
@@ -0,0 +1,78 @@
+/* -*- 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 "MsaaAccessible.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);
+
+ // 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<IUnknown>> 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..4dc4398008
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTable.cpp
@@ -0,0 +1,570 @@
+/* -*- 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 "IUnknownImpl.h"
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/TableAccessibleBase.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "Statistics.h"
+
+using namespace mozilla::a11y;
+
+TableAccessibleBase* ia2AccessibleTable::TableAcc() {
+ Accessible* acc = Acc();
+ return acc ? acc->AsTableBase() : nullptr;
+}
+
+// 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 ia2AccessibleHypertext::QueryInterface(iid, ppv);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ Accessible* caption = table->Caption();
+ if (!caption) return S_FALSE;
+
+ RefPtr<IAccessible> result = MsaaAccessible::GetFrom(caption);
+ result.forget(aAccessible);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_childIndex(long aRowIdx, long aColIdx,
+ long* aChildIdx) {
+ if (!aChildIdx) return E_INVALIDARG;
+
+ *aChildIdx = 0;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aRowIdx) >= table->RowCount() ||
+ static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ *aChildIdx = table->CellIndexAt(aRowIdx, aColIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_columnDescription(long aColIdx, BSTR* aDescription) {
+ if (!aDescription) return E_INVALIDARG;
+
+ *aDescription = nullptr;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ nsAutoString descr;
+ table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aRowIdx) >= table->RowCount() ||
+ static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ *aSpan = table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aCellIdx < 0) {
+ return E_INVALIDARG;
+ }
+
+ long colIdx = table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ *aColCount = table->ColCount();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nRows(long* aRowCount) {
+ if (!aRowCount) return E_INVALIDARG;
+
+ *aRowCount = 0;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ *aRowCount = table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ *aColCount = table->SelectedColCount();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nSelectedRows(long* aRowCount) {
+ if (!aRowCount) return E_INVALIDARG;
+
+ *aRowCount = 0;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ *aRowCount = table->SelectedRowCount();
+
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_rowDescription(long aRowIdx, BSTR* aDescription) {
+ if (!aDescription) return E_INVALIDARG;
+
+ *aDescription = nullptr;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= table->RowCount())
+ return E_INVALIDARG;
+
+ nsAutoString descr;
+ table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aRowIdx) >= table->RowCount() ||
+ static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ *aSpan = table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aCellIdx < 0) {
+ return E_INVALIDARG;
+ }
+
+ long rowIdx = table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<uint32_t, 30> cellIndices;
+ table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ *aIsSelected = table->IsColSelected(aColIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_isRowSelected(long aRowIdx, boolean* aIsSelected) {
+ if (!aIsSelected) return E_INVALIDARG;
+
+ *aIsSelected = false;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= table->RowCount())
+ return E_INVALIDARG;
+
+ *aIsSelected = table->IsRowSelected(aRowIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_isSelected(long aRowIdx, long aColIdx,
+ boolean* aIsSelected) {
+ if (!aIsSelected) return E_INVALIDARG;
+
+ *aIsSelected = false;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aColIdx) >= table->ColCount() ||
+ static_cast<uint32_t>(aRowIdx) >= table->RowCount())
+ return E_INVALIDARG;
+
+ *aIsSelected = table->IsCellSelected(aRowIdx, aColIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::selectRow(long aRowIdx) {
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= table->RowCount())
+ return E_INVALIDARG;
+
+ table->SelectRow(aRowIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::selectColumn(long aColIdx) {
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ table->SelectCol(aColIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::unselectRow(long aRowIdx) {
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= table->RowCount())
+ return E_INVALIDARG;
+
+ table->UnselectRow(aRowIdx);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::unselectColumn(long aColIdx) {
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= table->ColCount())
+ return E_INVALIDARG;
+
+ table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ if (aCellIdx < 0) {
+ return E_INVALIDARG;
+ }
+
+ int32_t colIdx = 0, rowIdx = 0;
+ table->RowAndColIndicesAt(aCellIdx, &rowIdx, &colIdx);
+ if (rowIdx == -1 || colIdx == -1) { // Indicates an error.
+ return E_INVALIDARG;
+ }
+
+ *aRowIdx = rowIdx;
+ *aColIdx = colIdx;
+ *aRowExtents = table->RowExtentAt(rowIdx, colIdx);
+ *aColExtents = table->ColExtentAt(rowIdx, colIdx);
+ *aIsSelected = table->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;
+
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ Accessible* cell = table->CellAt(aRowIdx, aColIdx);
+ if (!cell) return E_INVALIDARG;
+
+ RefPtr<IAccessible> result = MsaaAccessible::GetFrom(cell);
+ result.forget(aCell);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nSelectedCells(long* aCellCount) {
+ if (!aCellCount) return E_INVALIDARG;
+
+ *aCellCount = 0;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ *aCellCount = table->SelectedCellCount();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedCells(IUnknown*** aCells,
+ long* aNSelectedCells) {
+ if (!aCells || !aNSelectedCells) return E_INVALIDARG;
+
+ *aCells = nullptr;
+ *aNSelectedCells = 0;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<Accessible*, 30> cells;
+ table->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++) {
+ RefPtr<IAccessible> cell = MsaaAccessible::GetFrom(cells[i]);
+ cell.forget(&(*aCells)[i]);
+ }
+
+ *aNSelectedCells = cells.Length();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedColumns(long** aColumns, long* aNColumns) {
+ if (!aColumns || !aNColumns) return E_INVALIDARG;
+
+ *aColumns = nullptr;
+ *aNColumns = 0;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<uint32_t, 30> colIndices;
+ table->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;
+ TableAccessibleBase* table = TableAcc();
+ if (!table) return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<uint32_t, 30> rowIndices;
+ table->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..cc94e061ea
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTable.h
@@ -0,0 +1,178 @@
+/* -*- 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"
+#include "ia2AccessibleHypertext.h"
+#include "IUnknownImpl.h"
+
+namespace mozilla {
+namespace a11y {
+
+class TableAccessibleBase;
+
+class ia2AccessibleTable : public IAccessibleTable,
+ public IAccessibleTable2,
+ public ia2AccessibleHypertext {
+ public:
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+ IMPL_IUNKNOWN_REFCOUNTING_INHERITED(ia2AccessibleHypertext)
+
+ // 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:
+ using ia2AccessibleHypertext::ia2AccessibleHypertext;
+
+ private:
+ TableAccessibleBase* TableAcc();
+};
+
+} // 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..74b6c4233d
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTableCell.cpp
@@ -0,0 +1,186 @@
+/* -*- 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 "IUnknownImpl.h"
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/TableAccessibleBase.h"
+#include "mozilla/a11y/TableCellAccessibleBase.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using namespace mozilla::a11y;
+
+TableCellAccessibleBase* ia2AccessibleTableCell::CellAcc() {
+ Accessible* acc = Acc();
+ return acc ? acc->AsTableCellBase() : nullptr;
+}
+
+// IUnknown
+IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleTableCell)
+IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleTableCell)
+IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(ia2AccessibleHypertext)
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleTableCell
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_table(IUnknown** aTable) {
+ if (!aTable) return E_INVALIDARG;
+
+ *aTable = nullptr;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ TableAccessibleBase* table = tableCell->Table();
+ if (!table) return E_FAIL;
+
+ Accessible* tableAcc = table->AsAccessible();
+ RefPtr<IAccessible> result = MsaaAccessible::GetFrom(tableAcc);
+ result.forget(aTable);
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_columnExtent(long* aSpan) {
+ if (!aSpan) return E_INVALIDARG;
+
+ *aSpan = 0;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ *aSpan = tableCell->ColExtent();
+
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_columnHeaderCells(IUnknown*** aCellAccessibles,
+ long* aNColumnHeaderCells) {
+ if (!aCellAccessibles || !aNColumnHeaderCells) return E_INVALIDARG;
+
+ *aCellAccessibles = nullptr;
+ *aNColumnHeaderCells = 0;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<Accessible*, 10> cells;
+ tableCell->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++) {
+ RefPtr<IAccessible> iaCell = MsaaAccessible::GetFrom(cells[i]);
+ iaCell.forget(&(*aCellAccessibles)[i]);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_columnIndex(long* aColIdx) {
+ if (!aColIdx) return E_INVALIDARG;
+
+ *aColIdx = -1;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ *aColIdx = tableCell->ColIdx();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowExtent(long* aSpan) {
+ if (!aSpan) return E_INVALIDARG;
+
+ *aSpan = 0;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ *aSpan = tableCell->RowExtent();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowHeaderCells(IUnknown*** aCellAccessibles,
+ long* aNRowHeaderCells) {
+ if (!aCellAccessibles || !aNRowHeaderCells) return E_INVALIDARG;
+
+ *aCellAccessibles = nullptr;
+ *aNRowHeaderCells = 0;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<Accessible*, 10> cells;
+ tableCell->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++) {
+ RefPtr<IAccessible> iaCell = MsaaAccessible::GetFrom(cells[i]);
+ iaCell.forget(&(*aCellAccessibles)[i]);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowIndex(long* aRowIdx) {
+ if (!aRowIdx) return E_INVALIDARG;
+
+ *aRowIdx = -1;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ *aRowIdx = tableCell->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;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ *aRowIdx = tableCell->RowIdx();
+ *aColIdx = tableCell->ColIdx();
+ *aRowExtents = tableCell->RowExtent();
+ *aColExtents = tableCell->ColExtent();
+ *aIsSelected = tableCell->Selected();
+
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_isSelected(boolean* aIsSelected) {
+ if (!aIsSelected) return E_INVALIDARG;
+
+ *aIsSelected = false;
+ TableCellAccessibleBase* tableCell = CellAcc();
+ if (!tableCell) return CO_E_OBJNOTCONNECTED;
+
+ *aIsSelected = tableCell->Selected();
+ return S_OK;
+}
diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.h b/accessible/windows/ia2/ia2AccessibleTableCell.h
new file mode 100644
index 0000000000..b0cc00ce00
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTableCell.h
@@ -0,0 +1,71 @@
+/* -*- 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"
+#include "ia2AccessibleHypertext.h"
+#include "IUnknownImpl.h"
+
+namespace mozilla {
+namespace a11y {
+class TableCellAccessibleBase;
+
+class ia2AccessibleTableCell : public IAccessibleTableCell,
+ public ia2AccessibleHypertext {
+ public:
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+ IMPL_IUNKNOWN_REFCOUNTING_INHERITED(ia2AccessibleHypertext)
+
+ // 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:
+ using ia2AccessibleHypertext::ia2AccessibleHypertext;
+
+ private:
+ TableCellAccessibleBase* CellAcc();
+};
+
+} // 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..66b888bc63
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleText.cpp
@@ -0,0 +1,481 @@
+/* -*- 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 "ia2Accessible.h"
+#include "ia2AccessibleHypertext.h"
+#include "ia2AccessibleText.h"
+
+#include "AccessibleText_i.c"
+
+#include "HyperTextAccessibleWrap.h"
+#include "HyperTextAccessible-inl.h"
+#include "mozilla/ClearOnShutdown.h"
+
+using namespace mozilla::a11y;
+
+HyperTextAccessibleBase* ia2AccessibleText::sLastTextChangeAcc = nullptr;
+StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString;
+uint32_t ia2AccessibleText::sLastTextChangeStart = 0;
+uint32_t ia2AccessibleText::sLastTextChangeEnd = 0;
+bool ia2AccessibleText::sLastTextChangeWasInsert = false;
+
+HyperTextAccessibleBase* ia2AccessibleText::TextAcc() {
+ auto hyp = static_cast<ia2AccessibleHypertext*>(this);
+ Accessible* acc = hyp->Acc();
+ return acc ? acc->AsHyperTextBase() : nullptr;
+}
+
+std::pair<HyperTextAccessibleWrap*, HRESULT> ia2AccessibleText::LocalTextAcc() {
+ auto hyp = static_cast<ia2AccessibleHypertext*>(this);
+ Accessible* acc = hyp->Acc();
+ if (!acc) {
+ return {nullptr, CO_E_OBJNOTCONNECTED};
+ }
+ auto localAcc = static_cast<AccessibleWrap*>(acc->AsLocal());
+ if (!localAcc) {
+ return {nullptr, E_NOTIMPL};
+ }
+ return {static_cast<HyperTextAccessibleWrap*>(localAcc), S_OK};
+}
+
+// IAccessibleText
+
+STDMETHODIMP
+ia2AccessibleText::addSelection(long aStartOffset, long aEndOffset) {
+ auto [textAcc, hr] = LocalTextAcc();
+ if (!textAcc) {
+ return hr;
+ }
+
+ 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;
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ RefPtr<AccAttributes> attributes =
+ textAcc->TextAttributes(true, aOffset, &startOffset, &endOffset);
+
+ HRESULT hr =
+ ia2Accessible::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;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+ LayoutDeviceIntRect rect;
+ auto textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) 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) {
+ auto [textAcc, hr] = LocalTextAcc();
+ if (!textAcc) {
+ return hr;
+ }
+
+ return textAcc->RemoveFromSelection(aSelectionIndex) ? S_OK : E_INVALIDARG;
+}
+
+STDMETHODIMP
+ia2AccessibleText::setCaretOffset(long aOffset) {
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) {
+ 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) {
+ auto [textAcc, hr] = LocalTextAcc();
+ if (!textAcc) {
+ return hr;
+ }
+
+ return textAcc->SetSelectionBoundsAt(aSelectionIndex, aStartOffset,
+ aEndOffset)
+ ? S_OK
+ : E_INVALIDARG;
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_nCharacters(long* aNCharacters) {
+ if (!aNCharacters) return E_INVALIDARG;
+ *aNCharacters = 0;
+
+ HyperTextAccessibleBase* textAcc = TextAcc();
+ if (!textAcc) return CO_E_OBJNOTCONNECTED;
+
+ *aNCharacters = textAcc->CharacterCount();
+ return S_OK;
+}
+
+STDMETHODIMP
+ia2AccessibleText::scrollSubstringTo(long aStartIndex, long aEndIndex,
+ enum IA2ScrollType aScrollType) {
+ auto [textAcc, hr] = LocalTextAcc();
+ if (!textAcc) {
+ return hr;
+ }
+
+ 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;
+
+ auto [textAcc, hr] = LocalTextAcc();
+ if (!textAcc) {
+ return hr;
+ }
+
+ 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 != TextAcc()) 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(&sLastTextChangeString);
+}
+
+void ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleBase* aAcc,
+ bool aInsert,
+ const nsAString& 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..8b9bab04b3
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleText.h
@@ -0,0 +1,255 @@
+/* -*- 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 <utility>
+#include "AccessibleText.h"
+#include "nsIAccessibleText.h"
+
+namespace mozilla {
+template <class T>
+class StaticAutoPtr;
+template <class T>
+class StaticRefPtr;
+
+namespace a11y {
+class HyperTextAccessibleBase;
+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(HyperTextAccessibleBase* aAcc, bool aInsert,
+ const nsAString& aStr, int32_t aStart,
+ uint32_t aLen);
+
+ protected:
+ // This can't be a RefPtr because RemoteAccessibles aren't ref counted. It
+ // can't be an id because this is global and ids are only unique within the
+ // document. Since this is only used for comparison, we use a raw pointer.
+ // This should *never* be dereferenced, only used for comparison!
+ static HyperTextAccessibleBase* 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);
+ HyperTextAccessibleBase* TextAcc();
+
+ /**
+ * This can return null for two reasons. The HRESULT indicates the reason:
+ * CO_E_OBJNOTCONNECTED: The Accessible is dead.
+ * E_NOTIMPL: It isn't a LocalAccessible (so we can't support the method
+ * being called yet).
+ */
+ std::pair<HyperTextAccessibleWrap*, HRESULT> LocalTextAcc();
+};
+
+} // 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..ed8b7a4de2
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleValue.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "LocalAccessible-inl.h"
+#include "IUnknownImpl.h"
+
+#include "mozilla/FloatingPoint.h"
+
+using namespace mozilla::a11y;
+
+AccessibleWrap* ia2AccessibleValue::LocalAcc() {
+ return static_cast<MsaaAccessible*>(this)->LocalAcc();
+}
+
+Accessible* ia2AccessibleValue::Acc() {
+ return static_cast<MsaaAccessible*>(this)->Acc();
+}
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleValue::QueryInterface(REFIID iid, void** ppv) {
+ if (!ppv) return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleValue == iid) {
+ Accessible* valueAcc = Acc();
+ if (valueAcc && valueAcc->HasNumericValue()) {
+ RefPtr<IAccessibleValue> result = this;
+ result.forget(ppv);
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// IAccessibleValue
+
+STDMETHODIMP
+ia2AccessibleValue::get_currentValue(VARIANT* aCurrentValue) {
+ if (!aCurrentValue) return E_INVALIDARG;
+
+ VariantInit(aCurrentValue);
+
+ Accessible* valueAcc = Acc();
+ if (!valueAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ double currentValue;
+
+ 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 = LocalAcc();
+ if (!valueAcc) {
+ 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);
+
+ Accessible* valueAcc = Acc();
+ if (!valueAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ double maximumValue;
+
+ 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);
+
+ Accessible* valueAcc = Acc();
+ if (!valueAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ double minimumValue;
+
+ 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..368dbef244
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleValue.h
@@ -0,0 +1,43 @@
+/* -*- 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 AccessibleWrap;
+
+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);
+
+ private:
+ Accessible* Acc();
+ AccessibleWrap* LocalAcc();
+};
+
+} // 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..8c6ffdc7e9
--- /dev/null
+++ b/accessible/windows/ia2/moz.build
@@ -0,0 +1,61 @@
+# -*- 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",
+ "ia2AccessibleApplication.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")
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/accessible/windows/moz.build b/accessible/windows/moz.build
new file mode 100644
index 0000000000..53afb61f11
--- /dev/null
+++ b/accessible/windows/moz.build
@@ -0,0 +1,7 @@
+# -*- 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"]
diff --git a/accessible/windows/msaa/AccessibleWrap.cpp b/accessible/windows/msaa/AccessibleWrap.cpp
new file mode 100644
index 0000000000..679a3221ca
--- /dev/null
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -0,0 +1,289 @@
+/* -*- 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 "mozilla/a11y/DocAccessibleParent.h"
+#include "AccEvent.h"
+#include "GeckoCustom.h"
+#include "nsAccUtils.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIWidget.h"
+#include "nsWindowsHelpers.h"
+#include "mozilla/a11y/HyperTextAccessible.h"
+#include "mozilla/a11y/RemoteAccessible.h"
+#include "ServiceProvider.h"
+#include "sdnAccessible.h"
+
+#include "mozilla/mscom/AsyncInvoker.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+/* For documentation of the accessibility architecture,
+ * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
+ */
+
+StaticAutoPtr<nsTArray<AccessibleWrap::HandlerControllerData>>
+ AccessibleWrap::sHandlerControllers;
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : LocalAccessible(aContent, aDoc) {}
+
+NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, LocalAccessible)
+
+void AccessibleWrap::Shutdown() {
+ if (mMsaa) {
+ mMsaa->MsaaShutdown();
+ // Don't release mMsaa here because this will cause its id to be released
+ // immediately, which will result in immediate reuse, causing problems
+ // for clients. Instead, we release it in the destructor.
+ }
+ LocalAccessible::Shutdown();
+}
+
+//-----------------------------------------------------
+// IUnknown interface methods - see iunknown.h for documentation
+//-----------------------------------------------------
+
+MsaaAccessible* AccessibleWrap::GetMsaa() {
+ if (!mMsaa) {
+ mMsaa = MsaaAccessible::Create(this);
+ }
+ return mMsaa;
+}
+
+void AccessibleWrap::GetNativeInterface(void** aOutAccessible) {
+ RefPtr<IAccessible> result = GetMsaa();
+ return result.forget(aOutAccessible);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LocalAccessible
+
+nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
+ nsresult rv = LocalAccessible::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);
+
+ LocalAccessible* accessible = aEvent->GetAccessible();
+ if (!accessible) return NS_OK;
+
+ if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED ||
+ eventType == nsIAccessibleEvent::EVENT_FOCUS) {
+ UpdateSystemCaretFor(accessible);
+ }
+
+ MsaaAccessible::FireWinEvent(accessible, eventType);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap
+
+//------- Helper methods ---------
+
+bool AccessibleWrap::IsRootForHWND() {
+ if (IsRoot()) {
+ return true;
+ }
+ HWND thisHwnd = MsaaAccessible::GetHWNDFor(this);
+ AccessibleWrap* parent = static_cast<AccessibleWrap*>(LocalParent());
+ MOZ_ASSERT(parent);
+ HWND parentHwnd = MsaaAccessible::GetHWNDFor(parent);
+ return thisHwnd != parentHwnd;
+}
+
+void AccessibleWrap::UpdateSystemCaretFor(LocalAccessible* 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(
+ RemoteAccessible* 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.
+ LocalAccessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
+ UpdateSystemCaretFor(MsaaAccessible::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);
+ }
+}
+
+/* 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));
+ }
+ }
+}
+
+/* static */
+bool AccessibleWrap::DispatchTextChangeToHandler(Accessible* aAcc,
+ bool aIsInsert,
+ const nsAString& aText,
+ int32_t aStart,
+ uint32_t aLen) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+
+ if (!sHandlerControllers || sHandlerControllers->IsEmpty()) {
+ return false;
+ }
+
+ HWND hwnd = MsaaAccessible::GetHWNDFor(aAcc);
+ MOZ_ASSERT(hwnd);
+ if (!hwnd) {
+ return false;
+ }
+
+ long msaaId = MsaaAccessible::GetChildIDFor(aAcc);
+
+ 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(
+ reinterpret_cast<const wchar_t*>(aText.BeginReading()),
+ 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::SuppressHandlerA11yForClipboardCopy() {
+ if (!sHandlerControllers || sHandlerControllers->IsEmpty()) {
+ return;
+ }
+ // The original intent was that AccessibleHandler would be used in any
+ // process that wanted to access Gecko a11y. That didn't work out for various
+ // reasons. In practice, there is only a single AccessibleHandlerControl which
+ // is for our own parent process, used for in-process client calls. That also
+ // means we don't need to worry about async invokation here.
+ auto& controller = sHandlerControllers->ElementAt(0);
+ MOZ_ASSERT(controller.mPid == ::GetCurrentProcessId() &&
+ !controller.mIsProxy);
+ DebugOnly<HRESULT> hr = controller.mCtrl->SuppressA11yForClipboardCopy();
+ MOZ_ASSERT(SUCCEEDED(hr));
+}
diff --git a/accessible/windows/msaa/AccessibleWrap.h b/accessible/windows/msaa/AccessibleWrap.h
new file mode 100644
index 0000000000..8f9c944c92
--- /dev/null
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -0,0 +1,109 @@
+/* -*- 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 "LocalAccessible.h"
+#include "MsaaAccessible.h"
+#include "mozilla/a11y/AccessibleHandler.h"
+#include "mozilla/a11y/RemoteAccessible.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/StaticPtr.h"
+#include "nsXULAppAPI.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace a11y {
+class DocRemoteAccessibleWrap;
+
+/**
+ * Windows specific functionality for an accessibility tree node that originated
+ * in mDoc's content process.
+ */
+class AccessibleWrap : public LocalAccessible {
+ public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ public:
+ // LocalAccessible
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+ virtual void Shutdown() override;
+
+ // Helper methods
+ /**
+ * 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(LocalAccessible* aAccessible);
+ static void UpdateSystemCaretFor(RemoteAccessible* aProxy,
+ const LayoutDeviceIntRect& aCaretRect);
+
+ private:
+ static void UpdateSystemCaretFor(HWND aCaretWnd,
+ const LayoutDeviceIntRect& aCaretRect);
+
+ public:
+ /**
+ * Determine whether this is the root accessible for its HWND.
+ */
+ bool IsRootForHWND();
+
+ MsaaAccessible* GetMsaa();
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
+ static void SetHandlerControl(DWORD aPid, RefPtr<IHandlerControl> aCtrl);
+
+ static void InvalidateHandlers();
+
+ static bool DispatchTextChangeToHandler(Accessible* aAcc, bool aIsInsert,
+ const nsAString& aText,
+ int32_t aStart, uint32_t aLen);
+
+ static void SuppressHandlerA11yForClipboardCopy();
+
+ protected:
+ virtual ~AccessibleWrap() = default;
+
+ RefPtr<MsaaAccessible> mMsaa;
+
+ 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;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.cpp b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
new file mode 100644
index 0000000000..6a10a8bddc
--- /dev/null
+++ b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
@@ -0,0 +1,43 @@
+/* -*- 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 "nsIGfxInfo.h"
+#include "AccAttributes.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Components.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+NS_IMPL_ISUPPORTS_INHERITED0(ApplicationAccessibleWrap, ApplicationAccessible)
+
+already_AddRefed<AccAttributes> ApplicationAccessibleWrap::NativeAttributes() {
+ RefPtr<AccAttributes> attributes = new AccAttributes();
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ if (gfxInfo) {
+ bool isD2DEnabled = false;
+ gfxInfo->GetD2DEnabled(&isD2DEnabled);
+ RefPtr<nsAtom> attrName = NS_Atomize(u"D2D"_ns);
+ attributes->SetAttribute(attrName, isD2DEnabled);
+ }
+
+ return attributes.forget();
+}
+
+void ApplicationAccessibleWrap::Shutdown() {
+ // ApplicationAccessible::Shutdown doesn't call AccessibleWrap::Shutdown, so
+ // we have to call MsaaShutdown here.
+ if (mMsaa) {
+ mMsaa->MsaaShutdown();
+ }
+ ApplicationAccessible::Shutdown();
+}
diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.h b/accessible/windows/msaa/ApplicationAccessibleWrap.h
new file mode 100644
index 0000000000..b40c30fd15
--- /dev/null
+++ b/accessible/windows/msaa/ApplicationAccessibleWrap.h
@@ -0,0 +1,31 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace a11y {
+
+class ApplicationAccessibleWrap : public ApplicationAccessible {
+ ~ApplicationAccessibleWrap() {}
+
+ public:
+ // nsISupporst
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsAccessible
+ virtual already_AddRefed<AccAttributes> NativeAttributes() override;
+ virtual void Shutdown() override;
+};
+
+} // 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..ee00f39caa
--- /dev/null
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -0,0 +1,283 @@
+/* -*- 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 == static_cast<DWORD>(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);
+ }
+ }
+}
+
+// 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;
+ }
+ }
+}
+
+// Time when SuppressA11yForClipboardCopy() was called, as returned by
+// ::GetTickCount().
+static DWORD sA11yClipboardCopySuppressionStartTime = 0;
+
+/* static */
+void Compatibility::SuppressA11yForClipboardCopy() {
+ // Bug 1774285: Windows Suggested Actions (introduced in Windows 11 22H2)
+ // might walk the a11y tree using UIA whenever anything is copied to the
+ // clipboard. This causes an unacceptable hang, particularly when the cache
+ // is disabled.
+ bool doSuppress = [&] {
+ switch (
+ StaticPrefs::accessibility_windows_suppress_after_clipboard_copy()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return NeedsWindows11SuggestedActionsWorkaround();
+ }
+ }();
+
+ if (doSuppress) {
+ sA11yClipboardCopySuppressionStartTime = ::GetTickCount();
+ AccessibleWrap::SuppressHandlerA11yForClipboardCopy();
+ }
+}
+
+/* static */
+bool Compatibility::IsA11ySuppressedForClipboardCopy() {
+ constexpr DWORD kSuppressTimeout = 1500; // ms
+ if (!sA11yClipboardCopySuppressionStartTime) {
+ return false;
+ }
+ return ::GetTickCount() - sA11yClipboardCopySuppressionStartTime <
+ kSuppressTimeout;
+}
diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h
new file mode 100644
index 0000000000..072ecb4e0a
--- /dev/null
+++ b/accessible/windows/msaa/Compatibility.h
@@ -0,0 +1,125 @@
+/* -*- 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 <windows.h>
+#include "mozilla/Maybe.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 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);
+
+ static void SuppressA11yForClipboardCopy();
+ static bool IsA11ySuppressedForClipboardCopy();
+
+ 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..012dca650f
--- /dev/null
+++ b/accessible/windows/msaa/CompatibilityUIA.cpp
@@ -0,0 +1,339 @@
+/* -*- 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 "nsdefs.h"
+#include "nspr/prenv.h"
+
+#include "nsTHashMap.h"
+#include "nsTHashSet.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\\%lu\\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("_%08lx_%08" PRIxLPTR "_%08zx",
+ ::GetCurrentThreadId(), 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;
+ nsTHashSet<uint32_t> nonSectionObjTypes;
+ nsTHashMap<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.Insert(
+ 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.InsertOrUpdate(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..b1deae20bd
--- /dev/null
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -0,0 +1,106 @@
+/* -*- 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/Document.h"
+#include "DocAccessibleChild.h"
+#include "nsWinUtils.h"
+#include "RootAccessible.h"
+#include "sdnDocAccessible.h"
+#include "Statistics.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+DocAccessibleWrap::DocAccessibleWrap(dom::Document* aDocument,
+ PresShell* aPresShell)
+ : DocAccessible(aDocument, aPresShell), mHWND(nullptr) {}
+
+DocAccessibleWrap::~DocAccessibleWrap() {}
+
+////////////////////////////////////////////////////////////////////////////////
+// LocalAccessible
+
+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;
+ LayoutDeviceIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
+ if (Compatibility::IsDolphin()) {
+ rect = Bounds();
+ LayoutDeviceIntRect 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..5b1ae9d9b6
--- /dev/null
+++ b/accessible/windows/msaa/DocAccessibleWrap.h
@@ -0,0 +1,39 @@
+/* -*- 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();
+
+ // LocalAccessible
+ virtual void Shutdown();
+
+ // DocAccessible
+ virtual void* GetNativeWindow() const;
+
+ protected:
+ void* mHWND;
+
+ // DocAccessible
+ virtual void DoInitialUpdate();
+};
+
+} // 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..866e8f69a9
--- /dev/null
+++ b/accessible/windows/msaa/EnumVariant.cpp
@@ -0,0 +1,90 @@
+/* -*- 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(mAnchorMsaa)
+
+STDMETHODIMP
+ChildrenEnumVariant::Next(ULONG aCount, VARIANT FAR* aItems,
+ ULONG FAR* aCountFetched) {
+ if (!aItems || !aCountFetched) return E_INVALIDARG;
+
+ *aCountFetched = 0;
+
+ Accessible* anchor = mAnchorMsaa->Acc();
+ if (!anchor || anchor->ChildAt(mCurIndex) != mCurAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ ULONG countFetched = 0;
+ while (mCurAcc && countFetched < aCount) {
+ VariantInit(aItems + countFetched);
+
+ IDispatch* accNative = MsaaAccessible::NativeAccessible(mCurAcc);
+
+ ++mCurIndex;
+ mCurAcc = anchor->ChildAt(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) {
+ Accessible* anchor = mAnchorMsaa->Acc();
+ if (!anchor || anchor->ChildAt(mCurIndex) != mCurAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ mCurIndex += aCount;
+ mCurAcc = anchor->ChildAt(mCurIndex);
+
+ return mCurAcc ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP
+ChildrenEnumVariant::Reset() {
+ Accessible* anchor = mAnchorMsaa->Acc();
+ if (!anchor) return CO_E_OBJNOTCONNECTED;
+
+ mCurIndex = 0;
+ mCurAcc = anchor->ChildAt(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..b584380e50
--- /dev/null
+++ b/accessible/windows/msaa/EnumVariant.h
@@ -0,0 +1,62 @@
+/* -*- 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 "IUnknownImpl.h"
+#include "MsaaAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used to fetch accessible children.
+ */
+class ChildrenEnumVariant final : public IEnumVARIANT {
+ public:
+ explicit ChildrenEnumVariant(MsaaAccessible* aAnchor)
+ : mAnchorMsaa(aAnchor),
+ mCurAcc(mAnchorMsaa->Acc()->ChildAt(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)
+ : mAnchorMsaa(aEnumVariant.mAnchorMsaa),
+ mCurAcc(aEnumVariant.mCurAcc),
+ mCurIndex(aEnumVariant.mCurIndex) {}
+ virtual ~ChildrenEnumVariant() {}
+
+ protected:
+ RefPtr<MsaaAccessible> mAnchorMsaa;
+ 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..f85c27b724
--- /dev/null
+++ b/accessible/windows/msaa/GeckoCustom.cpp
@@ -0,0 +1,95 @@
+/* -*- 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 "GeckoCustom.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+IMPL_IUNKNOWN_QUERY_HEAD(GeckoCustom)
+IMPL_IUNKNOWN_QUERY_IFACE(IGeckoCustom)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mMsaa)
+
+HRESULT
+GeckoCustom::get_anchorCount(long* aCount) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aCount = acc->AnchorCount();
+ return S_OK;
+}
+
+HRESULT
+GeckoCustom::get_boundsInCSSPixels(int32_t* aX, int32_t* aY, int32_t* aWidth,
+ int32_t* aHeight) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ nsIntRect bounds = acc->BoundsInCSSPixels();
+ if (!bounds.IsEmpty()) {
+ *aWidth = bounds.Width();
+ *aHeight = bounds.Height();
+ }
+ // We should always report positional bounds info, even if
+ // our rect is empty.
+ *aX = bounds.X();
+ *aY = bounds.Y();
+
+ return S_OK;
+}
+
+HRESULT
+GeckoCustom::get_DOMNodeID(BSTR* aID) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ nsIContent* content = acc->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) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aID = acc->IsDoc() ? 0 : reinterpret_cast<uintptr_t>(acc);
+ return S_OK;
+}
+
+STDMETHODIMP
+GeckoCustom::get_minimumIncrement(double* aIncrement) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aIncrement = acc->Step();
+ return S_OK;
+}
+
+STDMETHODIMP
+GeckoCustom::get_mozState(uint64_t* aState) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aState = acc->State();
+ return S_OK;
+}
diff --git a/accessible/windows/msaa/GeckoCustom.h b/accessible/windows/msaa/GeckoCustom.h
new file mode 100644
index 0000000000..860c8b1482
--- /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 "IUnknownImpl.h"
+#include "IGeckoCustom.h"
+#include "MsaaAccessible.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(MsaaAccessible* aMsaa) : mMsaa(aMsaa) {}
+
+ // 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<MsaaAccessible> mMsaa;
+};
+
+} // 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..609f940b92
--- /dev/null
+++ b/accessible/windows/msaa/HyperTextAccessibleWrap.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "LocalAccessible-inl.h"
+
+#include "nsEventShell.h"
+
+#include "mozilla/StaticPtr.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessibleWrap, HyperTextAccessible)
+
+nsresult HyperTextAccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
+ uint32_t eventType = aEvent->GetEventType();
+
+ if (eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
+ eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED) {
+ LocalAccessible* 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..6f3b83383c
--- /dev/null
+++ b/accessible/windows/msaa/HyperTextAccessibleWrap.h
@@ -0,0 +1,34 @@
+/* -*- 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"
+
+namespace mozilla {
+
+namespace a11y {
+
+class HyperTextAccessibleWrap : public HyperTextAccessible {
+ public:
+ using HyperTextAccessible::HyperTextAccessible;
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // LocalAccessible
+ 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..7935ebedda
--- /dev/null
+++ b/accessible/windows/msaa/IUnknownImpl.h
@@ -0,0 +1,173 @@
+/* -*- 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() override { \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ ++mRefCnt; \
+ return mRefCnt; \
+ } \
+ ULONG STDMETHODCALLTYPE Release() override { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ --mRefCnt; \
+ if (mRefCnt) return mRefCnt; \
+ \
+ delete this; \
+ return 0; \
+ } \
+ \
+ private: \
+ mozilla::a11y::AutoRefCnt mRefCnt; \
+ \
+ public:
+
+#define DECL_IUNKNOWN_INHERITED \
+ public: \
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**);
+
+#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)
+
+/**
+ * Overrides AddRef and Release to call a specific base class.
+ * If you are inheriting a single class (e.g. to override some methods), you
+ * shouldn't need to use this. However, if you are inheriting from a COM
+ * implementation and also inheriting additional COM interfaces, you will need
+ * to use this to specify which base implements reference counting.
+ */
+#define IMPL_IUNKNOWN_REFCOUNTING_INHERITED(BaseClass) \
+ public: \
+ ULONG STDMETHODCALLTYPE AddRef() override { return BaseClass::AddRef(); } \
+ ULONG STDMETHODCALLTYPE Release() override { return BaseClass::Release(); }
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Converts nsresult to HRESULT.
+ */
+HRESULT GetHRESULT(nsresult aResult);
+
+} // 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..6b37b86369
--- /dev/null
+++ b/accessible/windows/msaa/LazyInstantiator.cpp
@@ -0,0 +1,755 @@
+/* -*- 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/LocalAccessible.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 "MsaaRootAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsWindowsHelpers.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsXPCOM.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::LocalAccessible* 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 LocalAccessible::GetNativeInterface.
+ rootAcc->GetNativeInterface(getter_AddRefs(result));
+ return result.forget();
+ }
+
+ auto msaaRoot =
+ static_cast<MsaaRootAccessible*>(MsaaAccessible::GetFrom(rootAcc));
+ // Subtle: msaaRoot 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.
+ RefPtr<IUnknown> punk(msaaRoot->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),
+ mWeakMsaaRoot(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(mWeakMsaaRoot->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
+ "rtop_bg.exe", // ByteFence Anti-Malware, bug 1713383
+};
+
+/**
+ * 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 (Compatibility::IsA11ySuppressedForClipboardCopy()) {
+ // Bug 1774285: Windows Suggested Actions (introduced in Windows 11 22H2)
+ // walks the entire a11y tree using UIA whenever anything is copied to the
+ // clipboard. This causes an unacceptable hang, particularly when the cache
+ // is disabled. Don't allow a11y to be instantiated by this.
+ return false;
+ }
+
+ 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;
+}
+
+MsaaRootAccessible* LazyInstantiator::ResolveMsaaRoot() {
+ LocalAccessible* acc = widget::WinUtils::GetRootAccessibleForHWND(mHwnd);
+ if (!acc || !acc->IsRoot()) {
+ return nullptr;
+ }
+
+ RefPtr<IAccessible> ia;
+ acc->GetNativeInterface(getter_AddRefs(ia));
+ return static_cast<MsaaRootAccessible*>(ia.get());
+}
+
+/**
+ * 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() {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT_UNREACHABLE("Called on a background thread!");
+ // Bug 1814780: This should never happen, since a caller should only be able
+ // to get this via AccessibleObjectFromWindow/AccessibleObjectFromEvent or
+ // WM_GETOBJECT/ObjectFromLresult, which should marshal any calls on
+ // a background thread to the main thread. Nevertheless, Windows sometimes
+ // calls QueryInterface from a background thread! To avoid crashes, fail
+ // gracefully here.
+ return RPC_E_WRONG_THREAD;
+ }
+
+ if (mWeakAccessible) {
+ return S_OK;
+ }
+
+ if (GetAccService() ||
+ ShouldInstantiate(mscom::ProcessRuntime::GetClientThreadId())) {
+ mWeakMsaaRoot = ResolveMsaaRoot();
+ if (!mWeakMsaaRoot) {
+ return E_POINTER;
+ }
+
+ // Wrap ourselves around the root accessible wrap
+ mRealRootUnk = mWeakMsaaRoot->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..b74c6c7467
--- /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 MsaaRootAccessible;
+
+/**
+ * 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();
+
+ MsaaRootAccessible* ResolveMsaaRoot();
+ 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.
+ */
+ MsaaRootAccessible* mWeakMsaaRoot;
+ IAccessible* mWeakAccessible;
+ IDispatch* mWeakDispatch;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_LazyInstantiator_h
diff --git a/accessible/windows/msaa/MsaaAccessible.cpp b/accessible/windows/msaa/MsaaAccessible.cpp
new file mode 100644
index 0000000000..851edbb6c3
--- /dev/null
+++ b/accessible/windows/msaa/MsaaAccessible.cpp
@@ -0,0 +1,1793 @@
+/* -*- 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"
+#include "ia2AccessibleApplication.h"
+#include "ia2AccessibleHypertext.h"
+#include "ia2AccessibleImage.h"
+#include "ia2AccessibleTable.h"
+#include "ia2AccessibleTableCell.h"
+#include "mozilla/a11y/AccessibleWrap.h"
+#include "mozilla/a11y/Compatibility.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/BrowserBridgeParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/mscom/Interceptor.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+#include "MsaaAccessible.h"
+#include "MsaaDocAccessible.h"
+#include "MsaaRootAccessible.h"
+#include "MsaaXULMenuAccessible.h"
+#include "nsEventMap.h"
+#include "nsViewManager.h"
+#include "nsWinUtils.h"
+#include "Relation.h"
+#include "sdnAccessible.h"
+#include "sdnTextAccessible.h"
+#include "HyperTextAccessible-inl.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+const uint32_t USE_ROLE_STRING = 0;
+static const VARIANT kVarChildIdSelf = {{{VT_I4}}};
+
+MsaaIdGenerator MsaaAccessible::sIDGen;
+ITypeInfo* MsaaAccessible::gTypeInfo = nullptr;
+
+/* static */
+MsaaAccessible* MsaaAccessible::Create(Accessible* aAcc) {
+ // If the cache is enabled, this should only ever be called in the parent
+ // process.
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup() ||
+ XRE_IsParentProcess());
+ if (!StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ aAcc->IsRemote()) {
+ // MsaaAccessible is just a stub to hold the id in this case.
+ return new MsaaAccessible(aAcc);
+ }
+ // The order of some of these is important! For example, when isRoot is true,
+ // IsDoc will also be true, so we must check IsRoot first. IsTable/Cell and
+ // IsHyperText are a similar case.
+ LocalAccessible* localAcc = aAcc->AsLocal();
+ if (localAcc && aAcc->IsRoot()) {
+ return new MsaaRootAccessible(aAcc);
+ }
+ if (aAcc->IsDoc()) {
+ return new MsaaDocAccessible(aAcc);
+ }
+ if (aAcc->IsTable()) {
+ return new ia2AccessibleTable(aAcc);
+ }
+ if (aAcc->IsTableCell()) {
+ return new ia2AccessibleTableCell(aAcc);
+ }
+ if (localAcc) {
+ // XXX These classes don't support RemoteAccessible yet.
+ if (aAcc->IsApplication()) {
+ return new ia2AccessibleApplication(aAcc);
+ }
+ if (aAcc->IsImage()) {
+ return new ia2AccessibleImage(aAcc);
+ }
+ if (localAcc->GetContent() &&
+ localAcc->GetContent()->IsXULElement(nsGkAtoms::menuitem)) {
+ return new MsaaXULMenuitemAccessible(aAcc);
+ }
+ }
+ if (aAcc->IsHyperText()) {
+ return new ia2AccessibleHypertext(aAcc);
+ }
+ return new MsaaAccessible(aAcc);
+}
+
+MsaaAccessible::MsaaAccessible(Accessible* aAcc) : mAcc(aAcc), mID(kNoID) {}
+
+MsaaAccessible::~MsaaAccessible() {
+ MOZ_ASSERT(!mAcc, "MsaaShutdown wasn't called!");
+ if (mID != kNoID) {
+ sIDGen.ReleaseID(WrapNotNull(this));
+ }
+}
+
+void MsaaAccessible::MsaaShutdown() {
+ // Accessibles can be shut down twice in some cases. If that happens,
+ // MsaaShutdown will also be called twice because AccessibleWrap holds
+ // the reference until its destructor is called; see the comments in
+ // AccessibleWrap::Shutdown.
+ if (!mAcc) {
+ return;
+ }
+
+ if (!StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ mAcc->IsRemote()) {
+ // This MsaaAccessible is just a stub. We just need to clear mAcc.
+ mAcc = nullptr;
+ return;
+ }
+
+ if (mID != kNoID) {
+ auto doc = MsaaDocAccessible::GetFromOwned(mAcc);
+ MOZ_ASSERT(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();
+ }
+
+ mAcc = nullptr;
+}
+
+void MsaaAccessible::SetID(uint32_t aID) {
+ MOZ_ASSERT(XRE_IsParentProcess() && mAcc &&
+ !StaticPrefs::accessibility_cache_enabled_AtStartup());
+ mID = aID;
+}
+
+int32_t MsaaAccessible::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;
+ }
+
+ // If the cache is disabled, chrome should use mID which has been generated by
+ // the content process.
+ if (!StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ aAccessible->IsRemote()) {
+ const uint32_t id = MsaaAccessible::GetFrom(aAccessible)->mID;
+ MOZ_ASSERT(id != kNoID);
+ return id;
+ }
+
+ auto doc = MsaaDocAccessible::GetFromOwned(aAccessible);
+ if (!doc) {
+ return 0;
+ }
+
+ uint32_t* id = &MsaaAccessible::GetFrom(aAccessible)->mID;
+ if (*id != kNoID) return *id;
+
+ *id = sIDGen.GetID();
+ doc->AddID(*id, aAccessible);
+
+ return *id;
+}
+
+/* static */
+uint32_t MsaaAccessible::GetContentProcessIdFor(
+ dom::ContentParentId aIPCContentId) {
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ return 0;
+ }
+ return sIDGen.GetContentProcessIDFor(aIPCContentId);
+}
+
+/* static */
+void MsaaAccessible::ReleaseContentProcessIdFor(
+ dom::ContentParentId aIPCContentId) {
+ sIDGen.ReleaseContentProcessIDFor(aIPCContentId);
+}
+
+/* static */
+void MsaaAccessible::AssignChildIDTo(NotNull<sdnAccessible*> aSdnAcc) {
+ aSdnAcc->SetUniqueID(sIDGen.GetID());
+}
+
+/* static */
+void MsaaAccessible::ReleaseChildID(NotNull<sdnAccessible*> aSdnAcc) {
+ sIDGen.ReleaseID(aSdnAcc);
+}
+
+HWND MsaaAccessible::GetHWNDFor(Accessible* aAccessible) {
+ if (!aAccessible) {
+ return nullptr;
+ }
+
+ LocalAccessible* localAcc = aAccessible->AsLocal();
+ if (!localAcc) {
+ RemoteAccessible* proxy = aAccessible->AsRemote();
+ 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.
+ LocalAccessible* 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 = localAcc->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 = localAcc->GetFrame();
+ if (frame) {
+ nsIWidget* widget = frame->GetNearestWidget();
+ if (widget && widget->IsVisible()) {
+ if (nsViewManager* vm = document->PresShellPtr()->GetViewManager()) {
+ nsCOMPtr<nsIWidget> rootWidget = vm->GetRootWidget();
+ // 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());
+}
+
+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 MsaaAccessible::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 = MsaaAccessible::GetChildIDFor(aTarget);
+ if (!childID) return; // Can't fire an event without a child ID
+
+ HWND hwnd = GetHWNDFor(aTarget);
+ if (!hwnd) {
+ return;
+ }
+
+ if (IsHandlerInvalidationNeeded(winEvent)) {
+ AccessibleWrap::InvalidateHandlers();
+ }
+
+ // Fire MSAA event for client area window.
+ ::NotifyWinEvent(winEvent, hwnd, OBJID_CLIENT, childID);
+}
+
+AccessibleWrap* MsaaAccessible::LocalAcc() {
+ if (!mAcc || mAcc->IsRemote()) {
+ return nullptr;
+ }
+ auto acc = static_cast<AccessibleWrap*>(mAcc);
+ MOZ_ASSERT(!acc || !acc->IsDefunct(),
+ "mAcc defunct but MsaaShutdown wasn't called");
+ return acc;
+}
+
+/**
+ * If this is an OOP iframe in a content process, return the COM proxy for the
+ * child document.
+ * This will only ever return something when the cache is disabled. When the
+ * cache is enabled, traversing to OOP iframe documents is handled in the
+ * parent process via the RemoteAccessible hierarchy.
+ */
+static already_AddRefed<IDispatch> MaybeGetOOPIframeDocCOMProxy(
+ Accessible* aAcc) {
+ if (!XRE_IsContentProcess() || !aAcc->IsOuterDoc()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+ LocalAccessible* outerDoc = aAcc->AsLocal();
+ auto bridge = dom::BrowserBridgeChild::GetFrom(outerDoc->GetContent());
+ if (!bridge) {
+ // This isn't an OOP iframe.
+ return nullptr;
+ }
+ return bridge->GetEmbeddedDocAccessible();
+}
+
+/**
+ * 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
+MsaaAccessible::ResolveChild(const VARIANT& aVarChild,
+ IAccessible** aOutInterface) {
+ MOZ_ASSERT(aOutInterface);
+ *aOutInterface = nullptr;
+
+ if (aVarChild.vt != VT_I4) {
+ return E_INVALIDARG;
+ }
+
+ if (!mAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ if (aVarChild.lVal == CHILDID_SELF) {
+ return S_OK;
+ }
+
+ if (aVarChild.lVal == 1 && RefPtr{MaybeGetOOPIframeDocCOMProxy(mAcc)}) {
+ // We can't access an OOP iframe document.
+ return E_FAIL;
+ }
+
+ 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;
+}
+
+static Accessible* GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID) {
+ Accessible* child = MsaaDocAccessible::GetFrom(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 Accessible* GetAccessibleInSubtree(DocAccessibleParent* aDoc,
+ uint32_t aID) {
+ Accessible* child = MsaaDocAccessible::GetFrom(aDoc)->GetAccessibleByID(aID);
+ if (child) {
+ return child;
+ }
+
+ size_t childDocCount = aDoc->ChildDocCount();
+ for (size_t i = 0; i < childDocCount; i++) {
+ child = GetAccessibleInSubtree(aDoc->ChildDocAt(i), aID);
+ if (child) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+static already_AddRefed<IDispatch> GetProxiedAccessibleInSubtree(
+ DocAccessibleParent* aDoc, const VARIANT& aVarChild) {
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+ RefPtr<IAccessible> comProxy;
+ int32_t docWrapperChildId = MsaaAccessible::GetChildIDFor(aDoc);
+ // Only document accessible proxies at the top level of their content process
+ // are created with a pointer to their COM proxy.
+ if (aDoc->IsTopLevelInContentProcess()) {
+ aDoc->GetCOMInterface(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();
+}
+
+static bool IsInclusiveDescendantOf(DocAccessible* aAncestor,
+ DocAccessible* aDescendant) {
+ for (DocAccessible* doc = aDescendant; doc; doc = doc->ParentDocument()) {
+ if (doc == aAncestor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+already_AddRefed<IAccessible> MsaaAccessible::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 (!mAcc) {
+ *aIsDefunct = true;
+ return nullptr;
+ }
+
+ AccessibleWrap* localAcc = static_cast<AccessibleWrap*>(mAcc->AsLocal());
+ if (varChild.lVal == CHILDID_SELF) {
+ MOZ_ASSERT(localAcc ||
+ StaticPrefs::accessibility_cache_enabled_AtStartup());
+ result = this;
+ return result.forget();
+ }
+
+ if (varChild.ulVal != GetExistingID() && nsAccUtils::MustPrune(mAcc)) {
+ // 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.
+ // 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() && localAcc && varChild.lVal < 0 &&
+ !sIDGen.IsChromeID(varChild.lVal)) {
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+ if (!localAcc->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.
+ if (varChild.lVal == 1) {
+ RefPtr<IDispatch> disp = MaybeGetOOPIframeDocCOMProxy(localAcc);
+ if (disp) {
+ // We need to return an IAccessible here, but
+ // MaybeGetOOPIframeDocCOMProxy returns an IDispatch. We know this
+ // IDispatch is also an IAccessible, so this code is safe, albeit ugly.
+ disp.forget((void**)getter_AddRefs(result));
+ return result.forget();
+ }
+ }
+ Accessible* xpAcc = mAcc->ChildAt(varChild.lVal - 1);
+ if (!xpAcc) {
+ return nullptr;
+ }
+ MOZ_ASSERT(xpAcc->IsRemote() || !xpAcc->AsLocal()->IsDefunct(),
+ "Shouldn't get a defunct child");
+ if (!StaticPrefs::accessibility_cache_enabled_AtStartup() && localAcc &&
+ xpAcc->IsRemote()) {
+ // We're crossing from a local OuterDoc to a remote document and caching
+ // isn't enabled. We must return the COM proxy.
+ MOZ_ASSERT(localAcc->IsOuterDoc());
+ xpAcc->AsRemote()->GetCOMInterface(getter_AddRefs(result));
+ return result.forget();
+ }
+ result = MsaaAccessible::GetFrom(xpAcc);
+ 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.
+ // First see about the case that both this accessible and the target one are
+ // RemoteAccessibles and the cache is disabled.
+ if (!localAcc && !StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ DocAccessibleParent* proxyDoc = mAcc->AsRemote()->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();
+ }
+
+ Accessible* doc = nullptr;
+ Accessible* child = nullptr;
+ auto id = static_cast<uint32_t>(varChild.lVal);
+ if (localAcc) {
+ DocAccessible* localDoc = localAcc->Document();
+ doc = localDoc;
+ child = GetAccessibleInSubtree(localDoc, id);
+ if (!child && StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ // Search remote documents which are descendants of this local document.
+ const auto remoteDocs = DocManager::TopLevelRemoteDocs();
+ if (!remoteDocs) {
+ return nullptr;
+ }
+ for (DocAccessibleParent* remoteDoc : *remoteDocs) {
+ LocalAccessible* outerDoc = remoteDoc->OuterDocOfRemoteBrowser();
+ if (!outerDoc ||
+ !IsInclusiveDescendantOf(localDoc, outerDoc->Document())) {
+ continue;
+ }
+ child = GetAccessibleInSubtree(remoteDoc, id);
+ if (child) {
+ break;
+ }
+ }
+ }
+ } else if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ DocAccessibleParent* remoteDoc = mAcc->AsRemote()->Document();
+ doc = remoteDoc;
+ child = GetAccessibleInSubtree(remoteDoc, id);
+ }
+ if (!child) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(child->IsLocal() ||
+ StaticPrefs::accessibility_cache_enabled_AtStartup());
+ MOZ_ASSERT(child->IsRemote() || !child->AsLocal()->IsDefunct(),
+ "Shouldn't get a defunct child");
+ // If this method is being called on the document we searched, we can just
+ // return child.
+ if (mAcc == doc) {
+ result = MsaaAccessible::GetFrom(child);
+ return result.forget();
+ }
+
+ // Otherwise, this method was called on a descendant, so we searched an
+ // ancestor. We must check whether child is really a descendant. This is used
+ // for ARIA documents and popups.
+ Accessible* parent = child;
+ while (parent && parent != doc) {
+ if (parent == mAcc) {
+ result = MsaaAccessible::GetFrom(child);
+ return result.forget();
+ }
+
+ parent = parent->Parent();
+ }
+
+ 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();
+ return std::all_of(bridges.cbegin(), bridges.cend(), [&](const auto& key) {
+ auto* bridge = static_cast<dom::BrowserBridgeParent*>(key);
+ dom::BrowserParent* childBrowser = bridge->GetBrowserParent();
+ DocAccessibleParent* childDocAcc = childBrowser->GetTopLevelDocAccessible();
+ if (!childDocAcc || childDocAcc->IsShutdown()) {
+ return true;
+ }
+ if (!aCallback(childDocAcc)) {
+ return false; // Stop traversal.
+ }
+ if (!VisitDocAccessibleParentDescendantsAtTopLevelInContentProcess(
+ childBrowser, aCallback)) {
+ return false; // Stop traversal.
+ }
+ return true; // Continue traversal.
+ });
+}
+
+already_AddRefed<IAccessible> MsaaAccessible::GetRemoteIAccessibleFor(
+ const VARIANT& aVarChild) {
+ a11y::RootAccessible* root = LocalAcc()->RootAccessible();
+ const nsTArray<DocAccessibleParent*>* rawRemoteDocs =
+ DocManager::TopLevelRemoteDocs();
+ if (!rawRemoteDocs) {
+ return nullptr;
+ }
+ nsTArray<RefPtr<DocAccessibleParent>> remoteDocs(rawRemoteDocs->Length());
+ for (auto rawRemoteDoc : *rawRemoteDocs) {
+ remoteDocs.AppendElement(rawRemoteDoc);
+ }
+
+ RefPtr<IAccessible> result;
+
+ for (auto topRemoteDoc : remoteDocs) {
+ if (topRemoteDoc->IsShutdown()) {
+ continue;
+ }
+ LocalAccessible* outerDoc = topRemoteDoc->OuterDocOfRemoteBrowser();
+ if (!outerDoc) {
+ continue;
+ }
+
+ if (outerDoc->RootAccessible() != root) {
+ continue;
+ }
+
+ RefPtr<IDispatch> disp;
+ auto checkDoc = [&aVarChild,
+ &disp](DocAccessibleParent* aRemoteDoc) -> bool {
+ uint32_t remoteDocMsaaId =
+ MsaaAccessible::GetFrom(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 LocalAccessible dies after we
+ // fetched disp but before we QI.
+ NS_WARNING_ASSERTION(SUCCEEDED(hr), "QI failed on remote IDispatch");
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+IDispatch* MsaaAccessible::NativeAccessible(Accessible* aAccessible) {
+ if (!aAccessible) {
+ NS_WARNING("Not passing in an aAccessible");
+ return nullptr;
+ }
+
+ RefPtr<IDispatch> disp;
+ if (!StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ aAccessible->IsRemote()) {
+ // This is a RemoteAccessible and caching isn't enabled. We must return
+ // the COM proxy.
+ aAccessible->AsRemote()->GetCOMInterface(getter_AddRefs(disp));
+ } else {
+ disp = MsaaAccessible::GetFrom(aAccessible);
+ }
+ IDispatch* rawDisp;
+ disp.forget(&rawDisp);
+ return rawDisp;
+}
+
+ITypeInfo* MsaaAccessible::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 */
+MsaaAccessible* MsaaAccessible::GetFrom(Accessible* aAcc) {
+ if (!aAcc) {
+ return nullptr;
+ }
+
+ if (RemoteAccessible* remoteAcc = aAcc->AsRemote()) {
+ return reinterpret_cast<MsaaAccessible*>(remoteAcc->GetWrapper());
+ }
+ return static_cast<AccessibleWrap*>(aAcc)->GetMsaa();
+}
+
+// IUnknown methods
+STDMETHODIMP
+MsaaAccessible::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;
+ }
+
+ // These interfaces are always available. We can support querying to them
+ // even if the Accessible is dead.
+ 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_IServiceProvider == iid) {
+ *ppv = new ServiceProvider(this);
+ } else {
+ HRESULT hr = ia2Accessible::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr)) {
+ return hr;
+ }
+ }
+ if (*ppv) {
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ // For interfaces below this point, we have to query the Accessible to
+ // determine if they are available.
+ if (!mAcc) {
+ // mscom::Interceptor (and maybe other callers) expects either S_OK or
+ // E_NOINTERFACE, so don't return CO_E_OBJNOTCONNECTED like we normally
+ // would for a dead object.
+ return E_NOINTERFACE;
+ }
+ AccessibleWrap* localAcc = LocalAcc();
+ if (IID_IEnumVARIANT == iid) {
+ if (
+ // Don't support this interface for leaf elements.
+ !mAcc->HasChildren() || nsAccUtils::MustPrune(mAcc) ||
+ // We also don't support this for local OuterDocAccessibles with remote
+ // document children when the cache is disabled. Handling this is tricky
+ // and there's no benefit to IEnumVARIANT when there's only one child
+ // anyway.
+ (localAcc && localAcc->IsOuterDoc() &&
+ !StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ localAcc->FirstChild()->IsRemote())) {
+ return E_NOINTERFACE;
+ }
+ *ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
+ } else if (IID_ISimpleDOMNode == iid) {
+ if (mAcc->IsDoc() || (localAcc && !localAcc->HasOwnContent())) {
+ return E_NOINTERFACE;
+ }
+
+ *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(WrapNotNull(this)));
+ } else if (iid == IID_ISimpleDOMText && localAcc && localAcc->IsTextLeaf()) {
+ statistics::ISimpleDOMUsed();
+ *ppv = static_cast<ISimpleDOMText*>(new sdnTextAccessible(this));
+ static_cast<IUnknown*>(*ppv)->AddRef();
+ return S_OK;
+ }
+
+ if (!*ppv && localAcc) {
+ HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr)) return hr;
+ }
+
+ if (!*ppv) {
+ HRESULT hr = ia2AccessibleHyperlink::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr)) return hr;
+ }
+
+ if (!*ppv) {
+ HRESULT hr = ia2AccessibleValue::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr)) return hr;
+ }
+
+ if (!*ppv && iid == IID_IGeckoCustom) {
+ MOZ_ASSERT(XRE_IsContentProcess() &&
+ !StaticPrefs::accessibility_cache_enabled_AtStartup());
+ 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
+MsaaAccessible::get_accParent(IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) {
+ if (!ppdispParent) return E_INVALIDARG;
+
+ *ppdispParent = nullptr;
+
+ if (!mAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ Accessible* xpParentAcc = mAcc->Parent();
+ if (!xpParentAcc) return S_FALSE;
+ MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup() ||
+ xpParentAcc->IsLocal());
+
+ *ppdispParent = NativeAccessible(xpParentAcc);
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::get_accChildCount(long __RPC_FAR* pcountChildren) {
+ if (!pcountChildren) return E_INVALIDARG;
+
+ *pcountChildren = 0;
+
+ if (!mAcc) return CO_E_OBJNOTCONNECTED;
+
+ if (Compatibility::IsA11ySuppressedForClipboardCopy() && mAcc->IsRoot()) {
+ // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 22H2)
+ // might walk the entire a11y tree using UIA whenever anything is copied to
+ // the clipboard. This causes an unacceptable hang, particularly when the
+ // cache is disabled. We prevent this tree walk by returning a 0 child count
+ // for the root window, from which Windows might walk.
+ return S_OK;
+ }
+
+ if (nsAccUtils::MustPrune(mAcc)) return S_OK;
+
+ *pcountChildren = mAcc->ChildCount();
+ if (*pcountChildren == 0 && RefPtr{MaybeGetOOPIframeDocCOMProxy(mAcc)}) {
+ *pcountChildren = 1;
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::get_accChild(
+ /* [in] */ VARIANT varChild,
+ /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispChild) {
+ if (!ppdispChild) return E_INVALIDARG;
+
+ *ppdispChild = nullptr;
+ if (!mAcc) 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;
+}
+
+STDMETHODIMP
+MsaaAccessible::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;
+ Acc()->Name(name);
+
+ if (name.IsVoid()) return S_FALSE;
+
+ *pszName = ::SysAllocStringLen(name.get(), name.Length());
+ if (!*pszName) return E_OUTOFMEMORY;
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::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;
+ Acc()->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
+MsaaAccessible::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;
+ Acc()->Description(description);
+
+ *pszDescription =
+ ::SysAllocStringLen(description.get(), description.Length());
+ return *pszDescription ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+MsaaAccessible::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
+ if (mAcc->IsLocal()) {
+ NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(mAcc->AsLocal()),
+ "Does not support Text when it should");
+ }
+#endif
+ geckoRole = mAcc->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 = mAcc->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
+ // XXX We should remove this hack and map to standard MSAA roles, even though
+ // they're lossy. See bug 798492.
+ if (mAcc->IsRemote()) {
+ // We don't support unknown or multiple ARIA roles for RemoteAccessible
+ // here, nor can we support namespaces. No one should be relying on this
+ // anyway, so this is fine. We just want to avoid returning a failure here.
+ nsAtom* val = nullptr;
+ const nsRoleMapEntry* roleMap = mAcc->ARIARoleMap();
+ if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) {
+ val = roleMap->roleAtom;
+ } else {
+ val = mAcc->TagName();
+ }
+ if (!val) {
+ return E_FAIL;
+ }
+ pvarRole->vt = VT_BSTR;
+ pvarRole->bstrVal = ::SysAllocString(val->GetUTF16String());
+ return S_OK;
+ }
+
+ LocalAccessible* localAcc = mAcc->AsLocal();
+ MOZ_ASSERT(localAcc);
+ nsIContent* content = localAcc->GetContent();
+ if (!content) return E_FAIL;
+
+ if (content->IsElement()) {
+ nsAutoString roleString;
+ // Try the role attribute.
+ nsAccUtils::GetARIAAttr(content->AsElement(), 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
+MsaaAccessible::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 = Acc()->State();
+
+ uint32_t msaaState = 0;
+ nsAccUtils::To32States(state, &msaaState, nullptr);
+ pvarState->lVal = msaaState;
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::get_accHelp(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR* pszHelp) {
+ if (!pszHelp) return E_INVALIDARG;
+
+ *pszHelp = nullptr;
+ return S_FALSE;
+}
+
+STDMETHODIMP
+MsaaAccessible::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
+MsaaAccessible::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 = mAcc->AccessKey();
+ if (keyBinding.IsEmpty()) {
+ if (LocalAccessible* localAcc = mAcc->AsLocal()) {
+ keyBinding = localAcc->KeyboardShortcut();
+ }
+ }
+
+ nsAutoString shortcut;
+ keyBinding.ToString(shortcut);
+
+ *pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), shortcut.Length());
+ return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+MsaaAccessible::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 (!mAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ // Return the current IAccessible child that has focus
+ Accessible* focusedAccessible = mAcc->FocusedChild();
+ if (focusedAccessible == mAcc) {
+ 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 = MsaaAccessible::NativeAccessible(mArray[mCurIndex]);
+ }
+
+ if (pceltFetched) *pceltFetched = celt;
+
+ return hr;
+}
+
+STDMETHODIMP
+AccessibleEnumerator::Clone(IEnumVARIANT FAR* FAR* ppenum) {
+ *ppenum = new AccessibleEnumerator(*this);
+ 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
+MsaaAccessible::get_accSelection(VARIANT __RPC_FAR* pvarChildren) {
+ if (!pvarChildren) return E_INVALIDARG;
+
+ VariantInit(pvarChildren);
+ pvarChildren->vt = VT_EMPTY;
+
+ if (!mAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ Accessible* acc = Acc();
+
+ if (!acc->IsSelect()) {
+ return S_OK;
+ }
+
+ AutoTArray<Accessible*, 10> selectedItems;
+ acc->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
+MsaaAccessible::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;
+ mAcc->ActionNameAt(0, defaultAction);
+
+ *pszDefaultAction =
+ ::SysAllocStringLen(defaultAction.get(), defaultAction.Length());
+ return *pszDefaultAction ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+MsaaAccessible::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.
+ MOZ_ASSERT(mAcc->IsLocal(),
+ "Content process should only have local accessibles");
+ nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod(
+ "LocalAccessible::TakeFocus", mAcc->AsLocal(),
+ &LocalAccessible::TakeFocus);
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+ return S_OK;
+ }
+ mAcc->TakeFocus();
+ return S_OK;
+ }
+
+ if (flagsSelect & SELFLAG_TAKESELECTION) {
+ mAcc->TakeSelection();
+ return S_OK;
+ }
+
+ if (flagsSelect & SELFLAG_ADDSELECTION) {
+ mAcc->SetSelected(true);
+ return S_OK;
+ }
+
+ if (flagsSelect & SELFLAG_REMOVESELECTION) {
+ mAcc->SetSelected(false);
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+STDMETHODIMP
+MsaaAccessible::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);
+ }
+
+ LayoutDeviceIntRect rect = Acc()->Bounds();
+ *pxLeft = rect.X();
+ *pyTop = rect.Y();
+ *pcxWidth = rect.Width();
+ *pcyHeight = rect.Height();
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::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);
+ }
+ // This should never be called on a RemoteAccessible if the cache is disabled.
+ MOZ_ASSERT(mAcc->IsLocal() ||
+ StaticPrefs::accessibility_cache_enabled_AtStartup());
+
+ 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 (RefPtr<IDispatch> disp = MaybeGetOOPIframeDocCOMProxy(mAcc)) {
+ pvarEndUpAt->vt = VT_DISPATCH;
+ disp.forget(&pvarEndUpAt->pdispVal);
+ return S_OK;
+ } else if (!nsAccUtils::MustPrune(mAcc)) {
+ navAccessible = mAcc->FirstChild();
+ }
+ break;
+ case NAVDIR_LASTCHILD:
+ if (RefPtr<IDispatch> disp = MaybeGetOOPIframeDocCOMProxy(mAcc)) {
+ pvarEndUpAt->vt = VT_DISPATCH;
+ disp.forget(&pvarEndUpAt->pdispVal);
+ return S_OK;
+ } else if (!nsAccUtils::MustPrune(mAcc)) {
+ navAccessible = mAcc->LastChild();
+ }
+ break;
+ case NAVDIR_NEXT:
+ navAccessible = mAcc->NextSibling();
+ break;
+ case NAVDIR_PREVIOUS:
+ navAccessible = mAcc->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 = mAcc->RelationByType(*xpRelation);
+ navAccessible = rel.Next();
+ }
+
+ if (!navAccessible) return E_FAIL;
+
+ pvarEndUpAt->pdispVal = NativeAccessible(navAccessible);
+ pvarEndUpAt->vt = VT_DISPATCH;
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::accHitTest(
+ /* [in] */ long xLeft,
+ /* [in] */ long yTop,
+ /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) {
+ if (!pvarChild) return E_INVALIDARG;
+
+ VariantInit(pvarChild);
+
+ if (!mAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ Accessible* accessible = mAcc->ChildAtPoint(
+ xLeft, yTop,
+ // The MSAA documentation says accHitTest should return a child. However,
+ // clients call AccessibleObjectFromPoint, which ends up walking the
+ // descendants calling accHitTest on each one. Since clients want the
+ // deepest descendant anyway, it's faster and probably more accurate to
+ // just do this ourselves. For now, we keep this behind the cache pref.
+ StaticPrefs::accessibility_cache_enabled_AtStartup()
+ ? Accessible::EWhichChildAtPoint::DeepestChild
+ : Accessible::EWhichChildAtPoint::DirectChild);
+
+ // if we got a child
+ if (accessible) {
+ if (accessible == mAcc) {
+ pvarChild->vt = VT_I4;
+ pvarChild->lVal = CHILDID_SELF;
+ } else {
+ pvarChild->vt = VT_DISPATCH;
+ pvarChild->pdispVal = NativeAccessible(accessible);
+ }
+ } else {
+ // no child at that point
+ if (RefPtr<IDispatch> disp = MaybeGetOOPIframeDocCOMProxy(mAcc)) {
+ // This is an OOP iframe. ChildAtPoint can't traverse inside it. If the
+ // coordinates are inside this iframe, return the COM proxy for the
+ // OOP document.
+ LayoutDeviceIntRect docRect = mAcc->AsLocal()->Bounds();
+ if (docRect.Contains(xLeft, yTop)) {
+ pvarChild->vt = VT_DISPATCH;
+ disp.forget(&pvarChild->pdispVal);
+ return S_OK;
+ }
+ }
+ pvarChild->vt = VT_EMPTY;
+ return S_FALSE;
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::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 mAcc->DoAction(0) ? S_OK : E_INVALIDARG;
+}
+
+STDMETHODIMP
+MsaaAccessible::put_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [in] */ BSTR szName) {
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+MsaaAccessible::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);
+ }
+ if (mAcc->IsRemote()) {
+ return E_NOTIMPL; // XXX Not supported for RemoteAccessible yet.
+ }
+
+ HyperTextAccessible* ht = LocalAcc()->AsHyperText();
+ if (!ht) {
+ return E_NOTIMPL;
+ }
+
+ uint32_t length = ::SysStringLen(szValue);
+ nsAutoString text(szValue, length);
+ ht->ReplaceText(text);
+ return S_OK;
+}
+
+// IDispatch methods
+
+STDMETHODIMP
+MsaaAccessible::GetTypeInfoCount(UINT* pctinfo) {
+ if (!pctinfo) return E_INVALIDARG;
+
+ *pctinfo = 1;
+ return S_OK;
+}
+
+STDMETHODIMP
+MsaaAccessible::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
+MsaaAccessible::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
+MsaaAccessible::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);
+}
diff --git a/accessible/windows/msaa/MsaaAccessible.h b/accessible/windows/msaa/MsaaAccessible.h
new file mode 100644
index 0000000000..5766d0e091
--- /dev/null
+++ b/accessible/windows/msaa/MsaaAccessible.h
@@ -0,0 +1,227 @@
+/* -*- 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_MsaaAccessible_h_
+#define mozilla_a11y_MsaaAccessible_h_
+
+#include "ia2Accessible.h"
+#include "ia2AccessibleComponent.h"
+#include "ia2AccessibleHyperlink.h"
+#include "ia2AccessibleValue.h"
+#include "IUnknownImpl.h"
+#include "mozilla/a11y/MsaaIdGenerator.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace a11y {
+class Accessible;
+class AccessibleWrap;
+class LocalAccessible;
+class sdnAccessible;
+
+class MsaaAccessible : public ia2Accessible,
+ public ia2AccessibleComponent,
+ public ia2AccessibleHyperlink,
+ public ia2AccessibleValue {
+ public:
+ static MsaaAccessible* Create(Accessible* aAcc);
+
+ Accessible* Acc() { return mAcc; }
+ AccessibleWrap* LocalAcc();
+
+ uint32_t GetExistingID() const { return mID; }
+ static const uint32_t kNoID = 0;
+ void SetID(uint32_t aID);
+
+ static int32_t GetChildIDFor(Accessible* aAccessible);
+ static uint32_t GetContentProcessIdFor(dom::ContentParentId aIPCContentId);
+ static void ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId);
+ static void AssignChildIDTo(NotNull<sdnAccessible*> aSdnAcc);
+ static void ReleaseChildID(NotNull<sdnAccessible*> aSdnAcc);
+ static HWND GetHWNDFor(Accessible* aAccessible);
+ static void FireWinEvent(Accessible* aTarget, uint32_t aEventType);
+
+ /**
+ * Find an accessible by the given child ID in cached documents.
+ */
+ [[nodiscard]] already_AddRefed<IAccessible> GetIAccessibleFor(
+ const VARIANT& aVarChild, bool* aIsDefunct);
+
+ /**
+ * Associate a COM object with this MsaaAccessible so it will be disconnected
+ * from remote clients when this MsaaAccessible shuts down.
+ * This should only be called with separate COM objects with a different
+ * IUnknown to this MsaaAccessible; 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);
+ }
+ }
+
+ void MsaaShutdown();
+
+ static IDispatch* NativeAccessible(Accessible* aAccessible);
+
+ static MsaaAccessible* GetFrom(Accessible* aAcc);
+
+ DECL_IUNKNOWN
+
+ // 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;
+
+ protected:
+ explicit MsaaAccessible(Accessible* aAcc);
+ virtual ~MsaaAccessible();
+
+ Accessible* mAcc;
+
+ uint32_t mID;
+ static MsaaIdGenerator sIDGen;
+
+ HRESULT
+ ResolveChild(const VARIANT& aVarChild, IAccessible** aOutInterface);
+
+ 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,
+ NAVRELATION_LINKS_TO = 0x1019
+ };
+
+ private:
+ /**
+ * Find a remote accessible by the given child ID.
+ */
+ [[nodiscard]] already_AddRefed<IAccessible> GetRemoteIAccessibleFor(
+ const VARIANT& aVarChild);
+
+ nsTArray<RefPtr<IUnknown>> mAssociatedCOMObjectsForDisconnection;
+
+ /**
+ * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it.
+ */
+ static ITypeInfo* GetTI(LCID lcid);
+ static ITypeInfo* gTypeInfo;
+};
+
+} // 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/MsaaDocAccessible.cpp b/accessible/windows/msaa/MsaaDocAccessible.cpp
new file mode 100644
index 0000000000..b0a2260b4c
--- /dev/null
+++ b/accessible/windows/msaa/MsaaDocAccessible.cpp
@@ -0,0 +1,173 @@
+/* -*- 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 "MsaaDocAccessible.h"
+
+#include "DocAccessibleChild.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+#include "nsAccUtils.h"
+#include "nsWinUtils.h"
+#include "Role.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+DocAccessible* MsaaDocAccessible::DocAcc() {
+ return static_cast<DocAccessible*>(LocalAcc());
+}
+
+/* static */
+MsaaDocAccessible* MsaaDocAccessible::GetFrom(DocAccessible* aDoc) {
+ return static_cast<MsaaDocAccessible*>(MsaaAccessible::GetFrom(aDoc));
+}
+
+/* static */
+MsaaDocAccessible* MsaaDocAccessible::GetFrom(DocAccessibleParent* aDoc) {
+ MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup());
+ return static_cast<MsaaDocAccessible*>(
+ reinterpret_cast<MsaaAccessible*>(aDoc->GetWrapper()));
+}
+
+/* static */
+MsaaDocAccessible* MsaaDocAccessible::GetFromOwned(Accessible* aAcc) {
+ if (RemoteAccessible* remoteAcc = aAcc->AsRemote()) {
+ DocAccessibleParent* doc = remoteAcc->Document();
+ if (!doc) {
+ return nullptr;
+ }
+ return MsaaDocAccessible::GetFrom(doc);
+ }
+ DocAccessible* doc = aAcc->AsLocal()->Document();
+ if (!doc) {
+ return nullptr;
+ }
+ return MsaaDocAccessible::GetFrom(doc);
+}
+
+// IUnknown
+IMPL_IUNKNOWN_QUERY_HEAD(MsaaDocAccessible)
+if (aIID == IID_ISimpleDOMDocument && LocalAcc()) {
+ statistics::ISimpleDOMUsed();
+ *aInstancePtr = static_cast<ISimpleDOMDocument*>(new sdnDocAccessible(this));
+ static_cast<IUnknown*>(*aInstancePtr)->AddRef();
+ return S_OK;
+}
+IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(ia2AccessibleHypertext)
+
+STDMETHODIMP
+MsaaDocAccessible::get_accParent(
+ /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) {
+ if (!mAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ if (mAcc->IsRemote()) {
+ MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup());
+ DocAccessibleParent* remoteDoc = mAcc->AsRemote()->AsDoc();
+ if (nsWinUtils::IsWindowEmulationStarted() && remoteDoc->IsTopLevel()) {
+ // Window emulation is enabled and this is a top level document. Return
+ // window system accessible object.
+ HWND hwnd = remoteDoc->GetEmulatedWindowHandle();
+ MOZ_ASSERT(hwnd);
+ if (hwnd &&
+ SUCCEEDED(::AccessibleObjectFromWindow(
+ hwnd, OBJID_WINDOW, IID_IAccessible, (void**)ppdispParent))) {
+ return S_OK;
+ }
+ }
+ return MsaaAccessible::get_accParent(ppdispParent);
+ }
+
+ DocAccessible* docAcc = DocAcc();
+ MOZ_ASSERT(docAcc);
+
+ // We might be a top-level document in a content process.
+ DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
+ if (ipcDoc && static_cast<dom::BrowserChild*>(ipcDoc->Manager())
+ ->GetTopLevelDocAccessibleChild() == ipcDoc) {
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+ // 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() &&
+ (!docAcc->ParentDocument() ||
+ (nsWinUtils::IsWindowEmulationStarted() &&
+ nsCoreUtils::IsTopLevelContentDocInProcess(docAcc->DocumentNode())))) {
+ HWND hwnd = static_cast<HWND>(docAcc->GetNativeWindow());
+ if (hwnd && !docAcc->ParentDocument()) {
+ nsIFrame* frame = docAcc->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 MsaaAccessible::get_accParent(ppdispParent);
+}
+
+STDMETHODIMP
+MsaaDocAccessible::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 = MsaaAccessible::get_accValue(aVarChild, aValue);
+ if (FAILED(hr) || *aValue || aVarChild.lVal != CHILDID_SELF) return hr;
+
+ // MsaaAccessible::get_accValue should have failed (and thus we should have
+ // returned early) if the Accessible is dead.
+ MOZ_ASSERT(mAcc);
+ // If document is being used to create a widget, don't use the URL hack
+ roles::Role role = mAcc->Role();
+ if (role != roles::DOCUMENT && role != roles::APPLICATION &&
+ role != roles::DIALOG && role != roles::ALERT &&
+ role != roles::NON_NATIVE_DOCUMENT)
+ return hr;
+
+ nsAutoString url;
+ nsAccUtils::DocumentURL(mAcc, url);
+ if (url.IsEmpty()) return S_FALSE;
+
+ *aValue = ::SysAllocStringLen(url.get(), url.Length());
+ return *aValue ? S_OK : E_OUTOFMEMORY;
+}
diff --git a/accessible/windows/msaa/MsaaDocAccessible.h b/accessible/windows/msaa/MsaaDocAccessible.h
new file mode 100644
index 0000000000..7aa9be62a4
--- /dev/null
+++ b/accessible/windows/msaa/MsaaDocAccessible.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_MsaaDocAccessible_h__
+#define mozilla_a11y_MsaaDocAccessible_h__
+
+#include "ia2AccessibleHypertext.h"
+
+namespace mozilla {
+
+namespace a11y {
+class Accessible;
+class DocAccessible;
+class DocAccessibleParent;
+
+class MsaaDocAccessible : public ia2AccessibleHypertext {
+ public:
+ DocAccessible* DocAcc();
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+ IMPL_IUNKNOWN_REFCOUNTING_INHERITED(ia2AccessibleHypertext)
+
+ // 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;
+
+ /**
+ * Manage the mapping from id to Accessible.
+ */
+ void AddID(uint32_t aID, Accessible* aAcc) {
+ mIDToAccessibleMap.InsertOrUpdate(aID, aAcc);
+ }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ Accessible* GetAccessibleByID(uint32_t aID) const {
+ return mIDToAccessibleMap.Get(aID);
+ }
+
+ static MsaaDocAccessible* GetFrom(DocAccessible* aDoc);
+ static MsaaDocAccessible* GetFrom(DocAccessibleParent* aDoc);
+
+ /**
+ * Get the MsaaDocAccessible for the document which owns the given Accessible.
+ */
+ static MsaaDocAccessible* GetFromOwned(Accessible* aAcc);
+
+ protected:
+ using ia2AccessibleHypertext::ia2AccessibleHypertext;
+
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsTHashMap<nsUint32HashKey, Accessible*> mIDToAccessibleMap;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/MsaaIdGenerator.cpp b/accessible/windows/msaa/MsaaIdGenerator.cpp
new file mode 100644
index 0000000000..2619ef39df
--- /dev/null
+++ b/accessible/windows/msaa/MsaaIdGenerator.cpp
@@ -0,0 +1,296 @@
+/* -*- 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/a11y/MsaaAccessible.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "nsAccessibilityService.h"
+#include "nsTHashMap.h"
+#include "sdnAccessible.h"
+
+static const uint32_t kNumFullIDBits = 31UL;
+
+// 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 = 8UL;
+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 nsTHashMap<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
+
+uint32_t MsaaIdGenerator::GetID() {
+ if (!mIDSet) {
+ // XXX This is only a pointer because we need to decide how many bits we
+ // need for the IDSet at runtime. Once the cache pref is gone, this no
+ // longer needs to be a pointer.
+ mIDSet =
+ MakeUnique<IDSet>(StaticPrefs::accessibility_cache_enabled_AtStartup()
+ ? kNumFullIDBits
+ : kNumUniqueIDBits);
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ // this is a static instance, so capturing this here is safe.
+ RunOnShutdown([this] {
+ if (mReleaseIDTimer) {
+ mReleaseIDTimer->Cancel();
+ ReleasePendingIDs();
+ }
+ });
+ }
+ }
+ uint32_t id = mIDSet->GetID();
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ MOZ_ASSERT(id <= ((1UL << kNumFullIDBits) - 1UL));
+ return ~id;
+ }
+ MOZ_ASSERT(id <= ((1UL << kNumUniqueIDBits) - 1UL));
+ return detail::BuildMsaaID(id, ResolveContentProcessID());
+}
+
+void MsaaIdGenerator::ReleasePendingIDs() {
+ for (auto id : mIDsToRelease) {
+ mIDSet->ReleaseID(~id);
+ }
+ mIDsToRelease.Clear();
+ mReleaseIDTimer = nullptr;
+}
+
+bool MsaaIdGenerator::ReleaseID(uint32_t aID) {
+ MOZ_ASSERT(aID != MsaaAccessible::kNoID);
+ if (!mIDSet) {
+ // If we're in the parent process and we're trying to release an id created
+ // in a content process, mIDSet might not exist yet. Just ignore this.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return false;
+ }
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ // Releasing an id means it can be reused. Reusing ids too quickly can
+ // cause problems for clients which process events asynchronously.
+ // Therefore, we release ids after a short delay. This doesn't seem to be
+ // necessary when the cache is disabled, perhaps because the COM runtime
+ // holds references to our objects for longer.
+ if (nsAccessibilityService::IsShutdown()) {
+ // If accessibility is shut down, no more Accessibles will be created.
+ // Also, if the service is shut down, it's possible XPCOM is also shutting
+ // down, in which case timers won't work. Thus, we release the id
+ // immediately.
+ mIDSet->ReleaseID(~aID);
+ return true;
+ }
+ const uint32_t kReleaseDelay = 1000;
+ mIDsToRelease.AppendElement(aID);
+ if (mReleaseIDTimer) {
+ mReleaseIDTimer->SetDelay(kReleaseDelay);
+ } else {
+ NS_NewTimerWithCallback(
+ getter_AddRefs(mReleaseIDTimer),
+ // mReleaseIDTimer is cancelled on shutdown and this is a static
+ // instance, so capturing this here is safe.
+ [this](nsITimer* aTimer) { ReleasePendingIDs(); }, kReleaseDelay,
+ nsITimer::TYPE_ONE_SHOT, "a11y::MsaaIdGenerator::ReleaseIDDelayed");
+ }
+ return true;
+ }
+ detail::MsaaIDCracker cracked(aID);
+ if (cracked.GetContentProcessId() != ResolveContentProcessID()) {
+ return false;
+ }
+ mIDSet->ReleaseID(cracked.GetUniqueId());
+ return true;
+}
+
+void MsaaIdGenerator::ReleaseID(NotNull<MsaaAccessible*> aMsaaAcc) {
+ // ReleaseID may fail 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.
+ ReleaseID(aMsaaAcc->GetExistingID());
+}
+
+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) {
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ return true;
+ }
+ 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());
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+ detail::MsaaIDCracker cracked(aID);
+ return cracked.GetContentProcessId() ==
+ GetContentProcessIDFor(aIPCContentProcessId);
+}
+
+bool MsaaIdGenerator::IsSameContentProcessFor(uint32_t aFirstID,
+ uint32_t aSecondID) {
+ MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
+ 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);
+ }
+
+ return sContentParentIdMap->LookupOrInsertWith(aIPCContentProcessID, [] {
+ uint32_t value = 0;
+ 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));
+
+ 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->Extract(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..d1f2c4974e
--- /dev/null
+++ b/accessible/windows/msaa/MsaaIdGenerator.h
@@ -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/. */
+
+#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"
+#include "mozilla/UniquePtr.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace a11y {
+
+class MsaaAccessible;
+class sdnAccessible;
+
+/**
+ * This class is responsible for generating child IDs used by our MSAA
+ * implementation.
+ *
+ * If the accessibility cache is disabled, we must differentiate IDs based on
+ * the originating process of the accessible, so 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.
+ *
+ * If the accessibility cache is enabled, we don't need to differentiate IDs
+ * based on the originating process, so all bits of the ID are used for the
+ * unique ID.
+ */
+class MsaaIdGenerator {
+ public:
+ uint32_t GetID();
+ void ReleaseID(NotNull<MsaaAccessible*> aMsaaAcc);
+ 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();
+ void ReleasePendingIDs();
+
+ private:
+ UniquePtr<IDSet> mIDSet;
+ nsTArray<uint32_t> mIDsToRelease;
+ nsCOMPtr<nsITimer> mReleaseIDTimer;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_MsaaIdGenerator_h
diff --git a/accessible/windows/msaa/MsaaRootAccessible.cpp b/accessible/windows/msaa/MsaaRootAccessible.cpp
new file mode 100644
index 0000000000..0698f892ef
--- /dev/null
+++ b/accessible/windows/msaa/MsaaRootAccessible.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/WindowsVersion.h"
+#include "MsaaRootAccessible.h"
+#include "Relation.h"
+#include "RootAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+RootAccessible* MsaaRootAccessible::RootAcc() {
+ return static_cast<RootAccessible*>(LocalAcc());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Aggregated IUnknown
+HRESULT
+MsaaRootAccessible::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 MsaaDocAccessible.
+ return MsaaDocAccessible::QueryInterface(aIid, aOutInterface);
+}
+
+ULONG
+MsaaRootAccessible::InternalAddRef() { return MsaaDocAccessible::AddRef(); }
+
+ULONG
+MsaaRootAccessible::InternalRelease() { return MsaaDocAccessible::Release(); }
+
+already_AddRefed<IUnknown> MsaaRootAccessible::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> MsaaRootAccessible::GetInternalUnknown() {
+ RefPtr<IUnknown> result(&mInternalUnknown);
+ return result.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MsaaRootAccessible
+STDMETHODIMP
+MsaaRootAccessible::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 MsaaDocAccessible::accNavigate(navDir, varStart, pvarEndUpAt);
+ }
+
+ if (!pvarEndUpAt) {
+ return E_INVALIDARG;
+ }
+ RootAccessible* rootAcc = RootAcc();
+ if (!rootAcc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ Accessible* target = nullptr;
+ // Get the document in the active tab.
+ RemoteAccessible* docProxy = rootAcc->GetPrimaryRemoteTopLevelContentDoc();
+ if (docProxy) {
+ target = docProxy;
+ } else {
+ // The base implementation could handle this, but we may as well
+ // just handle it here.
+ Relation rel = rootAcc->RelationByType(RelationType::EMBEDS);
+ target = rel.Next();
+ }
+
+ if (!target) {
+ return E_FAIL;
+ }
+
+ VariantInit(pvarEndUpAt);
+ pvarEndUpAt->pdispVal = NativeAccessible(target);
+ pvarEndUpAt->vt = VT_DISPATCH;
+ return S_OK;
+}
diff --git a/accessible/windows/msaa/MsaaRootAccessible.h b/accessible/windows/msaa/MsaaRootAccessible.h
new file mode 100644
index 0000000000..d2b3b89da1
--- /dev/null
+++ b/accessible/windows/msaa/MsaaRootAccessible.h
@@ -0,0 +1,55 @@
+/* -*- 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_MsaaRootAccessible_h__
+#define mozilla_a11y_MsaaRootAccessible_h__
+
+#include "mozilla/mscom/Aggregation.h"
+#include "MsaaDocAccessible.h"
+
+namespace mozilla {
+
+namespace a11y {
+
+class MsaaRootAccessible : public MsaaDocAccessible {
+ public:
+ explicit MsaaRootAccessible(Accessible* aAcc)
+ : MsaaDocAccessible(aAcc), mOuter(&mInternalUnknown) {}
+
+ /**
+ * 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;
+
+ private:
+ // DECLARE_AGGREGATABLE declares the internal IUnknown methods as well as
+ // mInternalUnknown.
+ DECLARE_AGGREGATABLE(MsaaRootAccessible);
+ IUnknown* mOuter;
+
+ RootAccessible* RootAcc();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/MsaaXULMenuAccessible.cpp b/accessible/windows/msaa/MsaaXULMenuAccessible.cpp
new file mode 100644
index 0000000000..5a6d41c1b8
--- /dev/null
+++ b/accessible/windows/msaa/MsaaXULMenuAccessible.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "MsaaXULMenuAccessible.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// MsaaXULMenuitemAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP
+MsaaXULMenuitemAccessible::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 MsaaAccessible::get_accKeyboardShortcut(varChild,
+ pszKeyboardShortcut);
+ }
+
+ LocalAccessible* acc = LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ KeyBinding keyBinding = acc->AccessKey();
+ if (keyBinding.IsEmpty()) {
+ return S_FALSE;
+ }
+
+ nsAutoString shortcut;
+ keyBinding.ToString(shortcut);
+
+ *pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), shortcut.Length());
+ return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY;
+}
+
+STDMETHODIMP
+MsaaXULMenuitemAccessible::get_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR* pszName) {
+ if (varChild.vt != VT_I4 || varChild.lVal != CHILDID_SELF) {
+ return MsaaAccessible::get_accName(varChild, pszName);
+ }
+ if (!pszName) {
+ return E_INVALIDARG;
+ }
+ LocalAccessible* acc = LocalAcc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ *pszName = nullptr;
+ nsAutoString name;
+ acc->Name(name);
+ if (name.IsVoid()) {
+ return S_FALSE;
+ }
+
+ nsAutoString accel;
+ if (dom::Element* el = acc->Elm()) {
+ el->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accel);
+ }
+ if (!accel.IsEmpty()) {
+ name += u"\t"_ns + accel;
+ }
+
+ *pszName = ::SysAllocStringLen(name.get(), name.Length());
+ if (!*pszName) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
diff --git a/accessible/windows/msaa/MsaaXULMenuAccessible.h b/accessible/windows/msaa/MsaaXULMenuAccessible.h
new file mode 100644
index 0000000000..ed5d5a4aa2
--- /dev/null
+++ b/accessible/windows/msaa/MsaaXULMenuAccessible.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_MsaaXULMenuAccessible_h__
+#define mozilla_a11y_MsaaXULMenuAccessible_h__
+
+#include "MsaaAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class MsaaXULMenuitemAccessible : public MsaaAccessible {
+ public:
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) override;
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR* pszName) override;
+
+ protected:
+ using MsaaAccessible::MsaaAccessible;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
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..ce8ab5501d
--- /dev/null
+++ b/accessible/windows/msaa/Platform.cpp
@@ -0,0 +1,407 @@
+/* -*- 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 <olectl.h>
+
+#include "AccEvent.h"
+#include "Compatibility.h"
+#include "HyperTextAccessibleWrap.h"
+#include "nsIWindowsRegKey.h"
+#include "nsWinUtils.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/a11y/RemoteAccessible.h"
+#include "mozilla/mscom/ActivationContext.h"
+#include "mozilla/mscom/InterceptorLog.h"
+#include "mozilla/mscom/Registration.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/StaticPrefs_accessibility.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsAccessibilityService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "WinUtils.h"
+
+#include <tuple>
+
+#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;
+
+static bool RegisterHandlerMsix() {
+ // If we're running in an MSIX container, the handler isn't registered in
+ // HKLM. We could do that via registry.dat, but that file is difficult to
+ // manage. Instead, we register it in HKCU if it isn't already. This also
+ // covers the case where we registered in HKCU in a previous run, but the
+ // MSIX was updated and the dll now has a different path. In that case,
+ // IsHandlerRegistered will return false because the paths are different,
+ // RegisterHandlerMsix will get called and it will correct the HKCU entry. We
+ // don't need to unregister because we'll always need this and Windows cleans
+ // up the registry data for an MSIX app when it is uninstalled.
+ nsCOMPtr<nsIFile> handlerPath;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(handlerPath));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = handlerPath->Append(u"AccessibleHandler.dll"_ns);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ nsAutoString path;
+ rv = handlerPath->GetPath(path);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsModuleHandle handlerDll(LoadLibrary(path.get()));
+ if (!handlerDll.get()) {
+ return false;
+ }
+ auto RegisterMsix = reinterpret_cast<decltype(&DllRegisterServer)>(
+ GetProcAddress(handlerDll, "RegisterMsix"));
+ if (!RegisterMsix) {
+ return false;
+ }
+ if (FAILED(RegisterMsix())) {
+ return false;
+ }
+ return true;
+}
+
+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();
+
+ if (XRE_IsParentProcess() && widget::WinUtils::HasPackageIdentity() &&
+ !IsHandlerRegistered()) {
+ // See the comments at the top of RegisterHandlerMsix regarding why we do
+ // this.
+ RegisterHandlerMsix();
+ }
+}
+
+void a11y::PlatformShutdown() {
+ ::DestroyCaret();
+
+ nsWinUtils::ShutdownWindowEmulation();
+ gRegCustomProxy = nullptr;
+ gRegProxy = nullptr;
+ gRegAccTlb = nullptr;
+ gRegMiscTlb = nullptr;
+
+ if (gInstantiator) {
+ gInstantiator = nullptr;
+ }
+}
+
+void a11y::ProxyCreated(RemoteAccessible* aProxy) {
+ MsaaAccessible* msaa = MsaaAccessible::Create(aProxy);
+ msaa->AddRef();
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(msaa));
+}
+
+void a11y::ProxyDestroyed(RemoteAccessible* aProxy) {
+ MsaaAccessible* msaa =
+ reinterpret_cast<MsaaAccessible*>(aProxy->GetWrapper());
+ if (!msaa) {
+ return;
+ }
+ msaa->MsaaShutdown();
+ aProxy->SetWrapper(0);
+ msaa->Release();
+
+ if (aProxy->IsDoc() && nsWinUtils::IsWindowEmulationStarted()) {
+ aProxy->AsDoc()->SetEmulatedWindowHandle(nullptr);
+ }
+}
+
+void a11y::ProxyEvent(RemoteAccessible* aTarget, uint32_t aEventType) {
+ MsaaAccessible::FireWinEvent(aTarget, aEventType);
+}
+
+void a11y::ProxyStateChangeEvent(RemoteAccessible* aTarget, uint64_t, bool) {
+ MsaaAccessible::FireWinEvent(aTarget, nsIAccessibleEvent::EVENT_STATE_CHANGE);
+}
+
+void a11y::ProxyFocusEvent(RemoteAccessible* aTarget,
+ const LayoutDeviceIntRect& aCaretRect) {
+ FocusManager* focusMgr = FocusMgr();
+ if (focusMgr && focusMgr->FocusedLocalAccessible()) {
+ // 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);
+ MsaaAccessible::FireWinEvent(aTarget, nsIAccessibleEvent::EVENT_FOCUS);
+}
+
+void a11y::ProxyCaretMoveEvent(RemoteAccessible* aTarget,
+ const LayoutDeviceIntRect& aCaretRect,
+ int32_t aGranularity) {
+ AccessibleWrap::UpdateSystemCaretFor(aTarget, aCaretRect);
+ MsaaAccessible::FireWinEvent(aTarget,
+ nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED);
+}
+
+void a11y::ProxyTextChangeEvent(RemoteAccessible* aText, const nsAString& aStr,
+ int32_t aStart, uint32_t aLen, bool aInsert,
+ bool) {
+ uint32_t eventType = aInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED
+ : nsIAccessibleEvent::EVENT_TEXT_REMOVED;
+ static const bool useHandler =
+ !StaticPrefs::accessibility_cache_enabled_AtStartup() &&
+ Preferences::GetBool("accessibility.handler.enabled", false) &&
+ IsHandlerRegistered();
+ if (useHandler) {
+ AccessibleWrap::DispatchTextChangeToHandler(aText, aInsert, aStr, aStart,
+ aLen);
+ return;
+ }
+
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ MOZ_ASSERT(aText->IsHyperText());
+ ia2AccessibleText::UpdateTextChangeData(aText->AsHyperTextBase(), aInsert,
+ aStr, aStart, aLen);
+ }
+ MsaaAccessible::FireWinEvent(aText, eventType);
+}
+
+void a11y::ProxyShowHideEvent(RemoteAccessible* aTarget, RemoteAccessible*,
+ bool aInsert, bool) {
+ uint32_t event =
+ aInsert ? nsIAccessibleEvent::EVENT_SHOW : nsIAccessibleEvent::EVENT_HIDE;
+ MsaaAccessible::FireWinEvent(aTarget, event);
+}
+
+void a11y::ProxySelectionEvent(RemoteAccessible* aTarget, RemoteAccessible*,
+ uint32_t aType) {
+ MsaaAccessible::FireWinEvent(aTarget, 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");
+
+ // If we're runnig in an MSIX container, we register this in HKCU, so look
+ // there.
+ const auto rootKey = widget::WinUtils::HasPackageIdentity()
+ ? nsIWindowsRegKey::ROOT_KEY_CURRENT_USER
+ : nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE;
+ rv = regKey->Open(rootKey, 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);
+}
+
+/**
+ * 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;
+ }
+
+ auto [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)
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AccessibilityClient,
+ NS_ConvertUTF16toUTF8(aValue));
+ }
+}
+
+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());
+}
+
+void a11y::SetInstantiator(const uint32_t aPid) {
+ nsCOMPtr<nsIFile> clientExe;
+ if (!GetInstantiatorExecutable(aPid, getter_AddRefs(clientExe))) {
+ AccumulateInstantiatorTelemetry(
+ u"(Failed to retrieve client image name)"_ns);
+ 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;
+
+ 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));
+}
+
+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..3a2477e877
--- /dev/null
+++ b/accessible/windows/msaa/RootAccessibleWrap.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "nsCoreUtils.h"
+#include "nsWinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor/destructor
+
+RootAccessibleWrap::RootAccessibleWrap(dom::Document* aDocument,
+ PresShell* aPresShell)
+ : RootAccessible(aDocument, aPresShell) {}
+
+RootAccessibleWrap::~RootAccessibleWrap() {}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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);
+ }
+ }
+}
diff --git a/accessible/windows/msaa/RootAccessibleWrap.h b/accessible/windows/msaa/RootAccessibleWrap.h
new file mode 100644
index 0000000000..20b3e8edfc
--- /dev/null
+++ b/accessible/windows/msaa/RootAccessibleWrap.h
@@ -0,0 +1,33 @@
+/* -*- 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 "RootAccessible.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+/**
+ * Windows specific functionality for the node at a root of the accessibility
+ * tree: see the RootAccessible superclass for further details.
+ */
+class RootAccessibleWrap : public RootAccessible {
+ public:
+ RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ // RootAccessible
+ virtual void DocumentActivated(DocAccessible* aDocument);
+};
+
+} // 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..98542a230c
--- /dev/null
+++ b/accessible/windows/msaa/ServiceProvider.cpp
@@ -0,0 +1,128 @@
+/* -*- 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 "AccessibleApplication_i.c"
+#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 "mozilla/StaticPrefs_accessibility.h"
+
+#include "ISimpleDOM.h"
+
+namespace mozilla {
+namespace a11y {
+
+IMPL_IUNKNOWN_QUERY_HEAD(ServiceProvider)
+IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mMsaa)
+
+////////////////////////////////////////////////////////////////////////////////
+// IServiceProvider
+
+STDMETHODIMP
+ServiceProvider::QueryService(REFGUID aGuidService, REFIID aIID,
+ void** aInstancePtr) {
+ if (!aInstancePtr) return E_INVALIDARG;
+
+ *aInstancePtr = nullptr;
+ Accessible* acc = mMsaa->Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AccessibleWrap* localAcc = mMsaa->LocalAcc();
+
+ // UIA IAccessibleEx
+ if (aGuidService == IID_IAccessibleEx &&
+ Preferences::GetBool("accessibility.uia.enable") && localAcc) {
+ uiaRawElmProvider* accEx = new uiaRawElmProvider(localAcc);
+ 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 acc is within an OOP iframe document, the top level document
+ // lives in a different process.
+ if (XRE_IsContentProcess()) {
+ RootAccessible* root = localAcc->RootAccessible();
+ // root will be null if acc 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;
+ }
+ }
+ }
+ }
+
+ MOZ_ASSERT(
+ acc->IsLocal() || StaticPrefs::accessibility_cache_enabled_AtStartup(),
+ "We should only handle remote accs here if the cache is on!");
+ Relation rel = acc->RelationByType(RelationType::CONTAINING_TAB_PANE);
+ RefPtr<IAccessible> next = MsaaAccessible::GetFrom(rel.Next());
+ if (!next) {
+ return E_NOINTERFACE;
+ }
+
+ next.forget(aInstancePtr);
+ 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;
+
+ RefPtr<IAccessible> appIa;
+ applicationAcc->GetNativeInterface(getter_AddRefs(appIa));
+ return appIa->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 mMsaa->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..0545bccf62
--- /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 "IUnknownImpl.h"
+#include "MsaaAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ServiceProvider final : public IServiceProvider {
+ public:
+ explicit ServiceProvider(MsaaAccessible* aMsaa) : mMsaa(aMsaa) {}
+ ~ServiceProvider() {}
+
+ DECL_IUNKNOWN
+
+ // IServiceProvider
+ virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID aGuidService,
+ REFIID aIID,
+ void** aInstancePtr);
+
+ private:
+ RefPtr<MsaaAccessible> mMsaa;
+};
+
+} // 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..264504670e
--- /dev/null
+++ b/accessible/windows/msaa/moz.build
@@ -0,0 +1,80 @@
+# -*- 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",
+ "MsaaAccessible.h",
+ "MsaaIdGenerator.h",
+ "nsWinUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "AccessibleWrap.cpp",
+ "ApplicationAccessibleWrap.cpp",
+ "Compatibility.cpp",
+ "CompatibilityUIA.cpp",
+ "DocAccessibleWrap.cpp",
+ "EnumVariant.cpp",
+ "GeckoCustom.cpp",
+ "HyperTextAccessibleWrap.cpp",
+ "IUnknownImpl.cpp",
+ "MsaaAccessible.cpp",
+ "MsaaDocAccessible.cpp",
+ "MsaaIdGenerator.cpp",
+ "MsaaRootAccessible.cpp",
+ "MsaaXULMenuAccessible.cpp",
+ "nsWinUtils.cpp",
+ "Platform.cpp",
+ "RootAccessibleWrap.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",
+]
+
+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"
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/accessible/windows/msaa/nsEventMap.h b/accessible/windows/msaa/nsEventMap.h
new file mode 100644
index 0000000000..018ac92325
--- /dev/null
+++ b/accessible/windows/msaa/nsEventMap.h
@@ -0,0 +1,110 @@
+/* -*- 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
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_STYLING_CHANGED
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_INNER_REORDER
+ // clang-format on
+};
diff --git a/accessible/windows/msaa/nsWinUtils.cpp b/accessible/windows/msaa/nsWinUtils.cpp
new file mode 100644
index 0000000000..14aa023c73
--- /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 "mozilla/StaticPrefs_accessibility.h"
+#include "nsArrayUtils.h"
+#include "nsICSSDeclaration.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsXULAppAPI.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) {
+ RefPtr<IAccessible> msaaAccessible;
+ DocAccessible* document =
+ reinterpret_cast<DocAccessible*>(::GetPropW(hWnd, kPropNameDocAcc));
+ if (document) {
+ document->GetNativeInterface(getter_AddRefs(msaaAccessible));
+ } else {
+ DocAccessibleParent* docParent = static_cast<DocAccessibleParent*>(
+ ::GetPropW(hWnd, kPropNameDocAccParent));
+ if (docParent) {
+ if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+ msaaAccessible = MsaaAccessible::GetFrom(docParent);
+ } else {
+ docParent->GetCOMInterface(getter_AddRefs(msaaAccessible));
+ }
+ }
+ }
+ if (msaaAccessible) {
+ LRESULT result =
+ ::LresultFromObject(IID_IAccessible, wParam,
+ msaaAccessible); // does an 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..4d6ed60a18
--- /dev/null
+++ b/accessible/windows/sdn/moz.build
@@ -0,0 +1,26 @@
+# -*- 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"
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/accessible/windows/sdn/sdnAccessible-inl.h b/accessible/windows/sdn/sdnAccessible-inl.h
new file mode 100644
index 0000000000..76f8796f42
--- /dev/null
+++ b/accessible/windows/sdn/sdnAccessible-inl.h
@@ -0,0 +1,49 @@
+/* -*- 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 {
+ MOZ_ASSERT(mNode);
+ return GetExistingDocAccessible(mNode->OwnerDoc());
+}
+
+inline MsaaAccessible* sdnAccessible::GetMsaa() {
+ if (mMsaa) {
+ return mMsaa;
+ }
+
+ 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.
+ AccessibleWrap* wrap = static_cast<AccessibleWrap*>(
+ document->GetAccessibleEvenIfNotInMap(mNode));
+
+ if (!wrap) {
+ return nullptr;
+ }
+
+ wrap->GetNativeInterface(getter_AddRefs(mMsaa));
+ return mMsaa;
+}
+
+} // 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..c1bbbb7e2f
--- /dev/null
+++ b/accessible/windows/sdn/sdnAccessible.cpp
@@ -0,0 +1,522 @@
+/* -*- 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()) {
+ MsaaAccessible::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;
+ }
+
+ MsaaAccessible* accessible = GetMsaa();
+ 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;
+
+ // 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.
+ MsaaAccessible* accessible = GetMsaa();
+ if (accessible) {
+ *aUniqueID = MsaaAccessible::GetChildIDFor(accessible->Acc());
+ } else {
+ if (mUniqueId.isNothing()) {
+ MsaaAccessible::AssignChildIDTo(WrapNotNull(this));
+ }
+ MOZ_ASSERT(mUniqueId.isSome());
+ *aUniqueID = mUniqueId.value();
+ }
+
+ if (!mNode) {
+ RemoteAccessible* remoteAcc = accessible->Acc()->AsRemote();
+ MOZ_ASSERT(remoteAcc);
+ if (remoteAcc->IsText()) {
+ *aNodeType = nsINode::TEXT_NODE;
+ } else if (remoteAcc->IsDoc()) {
+ *aNodeType = nsINode::DOCUMENT_NODE;
+ } else {
+ *aNodeType = nsINode::ELEMENT_NODE;
+ }
+ if (nsAtom* tag = remoteAcc->TagName()) {
+ nsAutoString nodeName;
+ tag->ToString(nodeName);
+ *aNodeName = ::SysAllocString(nodeName.get());
+ }
+ return S_OK;
+ }
+
+ 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;
+
+ *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) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ // NVDA expects this to succeed for MathML and won't call innerHTML if this
+ // fails. Therefore, return S_FALSE here instead of E_NOTIMPL, indicating
+ // that the attributes aren't present.
+ return S_FALSE;
+ }
+
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ *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) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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) {
+ if (IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ DocAccessible* document = GetDocument();
+ MOZ_ASSERT(document);
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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;
+
+ nsAutoString innerHTML;
+ if (!mNode) {
+ RemoteAccessible* remoteAcc = mMsaa->Acc()->AsRemote();
+ MOZ_ASSERT(remoteAcc);
+ if (!remoteAcc->mCachedFields) {
+ return S_FALSE;
+ }
+ remoteAcc->mCachedFields->GetAttribute(nsGkAtoms::html, innerHTML);
+ } else {
+ if (!mNode->IsElement()) {
+ return S_FALSE;
+ }
+ 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;
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return E_NOTIMPL;
+ }
+
+ 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..610a4ca5de
--- /dev/null
+++ b/accessible/windows/sdn/sdnAccessible.h
@@ -0,0 +1,149 @@
+/* -*- 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 "MsaaAccessible.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<MsaaAccessible*> aMsaa) : mMsaa(aMsaa) {
+ Accessible* acc = aMsaa->Acc();
+ MOZ_ASSERT(acc);
+ if (LocalAccessible* localAcc = acc->AsLocal()) {
+ mNode = localAcc->GetNode();
+ }
+ }
+
+ ~sdnAccessible();
+
+ /**
+ * Return if the object is defunct.
+ */
+ bool IsDefunct() const {
+ if (mMsaa && !mMsaa->Acc()) {
+ return true;
+ }
+ if (!mNode) {
+ MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
+ return false;
+ }
+ return !GetDocument();
+ }
+
+ /**
+ * Return a local document accessible it belongs to if any.
+ */
+ DocAccessible* GetDocument() const;
+
+ /*
+ * Return associated MsaaAccessible if any.
+ */
+ MsaaAccessible* GetMsaa();
+
+ 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:
+ // mNode will be null for a RemoteAccessible. In that case, we only partially
+ // implement this interface using data from the RemoteAccessible cache.
+ nsCOMPtr<nsINode> mNode;
+ RefPtr<MsaaAccessible> mMsaa;
+ 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..e773f1a5a0
--- /dev/null
+++ b/accessible/windows/sdn/sdnDocAccessible.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "LocalAccessible-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(mMsaa)
+
+STDMETHODIMP
+sdnDocAccessible::get_URL(BSTR __RPC_FAR* aURL) {
+ if (!aURL) return E_INVALIDARG;
+ *aURL = nullptr;
+
+ DocAccessible* acc = mMsaa->DocAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString URL;
+ acc->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;
+
+ DocAccessible* acc = mMsaa->DocAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString title;
+ acc->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;
+
+ DocAccessible* acc = mMsaa->DocAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString mimeType;
+ acc->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;
+
+ DocAccessible* acc = mMsaa->DocAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString docType;
+ acc->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 (!mMsaa->DocAcc()) 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 !mMsaa->DocAcc() ? 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..452b43ec85
--- /dev/null
+++ b/accessible/windows/sdn/sdnDocAccessible.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_sdnDocAccessible_h_
+#define mozilla_a11y_sdnDocAccessible_h_
+
+#include "ISimpleDOM.h"
+#include "IUnknownImpl.h"
+
+#include "MsaaDocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class sdnDocAccessible final : public ISimpleDOMDocument {
+ public:
+ explicit sdnDocAccessible(MsaaDocAccessible* aMsaa) : mMsaa(aMsaa){};
+ ~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<MsaaDocAccessible> mMsaa;
+};
+
+} // 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..73ef4eb6e8
--- /dev/null
+++ b/accessible/windows/sdn/sdnTextAccessible.cpp
@@ -0,0 +1,166 @@
+/* -*- 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(mMsaa)
+
+STDMETHODIMP
+sdnTextAccessible::get_domText(BSTR __RPC_FAR* aText) {
+ if (!aText) return E_INVALIDARG;
+ *aText = nullptr;
+
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString nodeValue;
+
+ acc->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 = mMsaa->LocalAcc()->Document();
+ NS_ASSERTION(
+ document,
+ "There must always be a doc accessible, but there isn't. Crash!");
+
+ LayoutDeviceIntRect docRect = document->Bounds();
+ LayoutDeviceIntRect unclippedRect(x, y, width, height);
+
+ LayoutDeviceIntRect 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;
+
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsIFrame* frame = acc->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 = acc->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) {
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ RefPtr<nsRange> range = nsRange::Create(acc->GetContent());
+ if (NS_FAILED(range->SetStart(acc->GetContent(), aStartIndex))) return E_FAIL;
+
+ if (NS_FAILED(range->SetEnd(acc->GetContent(), aEndIndex))) return E_FAIL;
+
+ nsresult rv = nsCoreUtils::ScrollSubstringTo(
+ acc->GetFrame(), range, nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+ return GetHRESULT(rv);
+}
+
+STDMETHODIMP
+sdnTextAccessible::get_fontFamily(BSTR __RPC_FAR* aFontFamily) {
+ if (!aFontFamily) return E_INVALIDARG;
+ *aFontFamily = nullptr;
+
+ AccessibleWrap* acc = mMsaa->LocalAcc();
+ if (!acc) return CO_E_OBJNOTCONNECTED;
+
+ nsIFrame* frame = acc->GetFrame();
+ if (!frame) return E_FAIL;
+
+ RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(frame, 1.0f);
+ RefPtr<gfxFont> font = fm->GetThebesFontGroup()->GetFirstValidFont();
+
+ const nsCString& name = font->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..3f93c20fcc
--- /dev/null
+++ b/accessible/windows/sdn/sdnTextAccessible.h
@@ -0,0 +1,69 @@
+/* -*- 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 "MsaaAccessible.h"
+
+class nsIFrame;
+struct nsPoint;
+
+namespace mozilla {
+namespace a11y {
+
+class sdnTextAccessible final : public ISimpleDOMText {
+ public:
+ explicit sdnTextAccessible(MsaaAccessible* aMsaa) : mMsaa(aMsaa){};
+ ~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<MsaaAccessible> mMsaa;
+};
+
+} // 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..c78dbca7fe
--- /dev/null
+++ b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -0,0 +1,206 @@
+/* -*- 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 "AccAttributes.h"
+#include "AccessibleWrap.h"
+#include "ARIAMap.h"
+#include "LocalAccessible-inl.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<IAccessible> copy;
+ mAcc->GetNativeInterface(getter_AddRefs(copy));
+ 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;
+
+ RefPtr<AccAttributes> attributes = mAcc->Attributes();
+ attributes->GetAttribute(nsGkAtoms::xmlroles, 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());
+ while (attribIter.Next()) {
+ nsAutoString attribName, attribValue;
+ nsAutoString value;
+ attribIter.AttrName()->ToString(attribName);
+ attribIter.AttrValue(attribValue);
+ if (StringBeginsWith(attribName, u"aria-"_ns)) {
+ // Found 'aria-'
+ attribName.ReplaceLiteral(0, 5, u"");
+ }
+
+ 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