Bug 1759971: Support MathML on a cached RemoteAccessible for Windows a11y clients. r=morgan

Windows a11y clients retrieve MathML markup using ISimpleDOMNode::innerHTML.
We cache innerHTML to support this, but only on math elements and only on Windows.
sdnAccessible had to be modified to support RemoteAccessible and to use the cache for this method.

Differential Revision: https://phabricator.services.mozilla.com/D155806
This commit is contained in:
James Teh 2022-08-30 02:29:17 +00:00
Родитель 551e1f9ba8
Коммит 0787850ef9
9 изменённых файлов: 183 добавлений и 11 удалений

Просмотреть файл

@ -31,6 +31,10 @@ class CacheDomain {
static constexpr uint64_t Viewport = ((uint64_t)0x1) << 14;
static constexpr uint64_t ARIA = ((uint64_t)0x1) << 15;
static constexpr uint64_t Relations = ((uint64_t)0x1) << 16;
#ifdef XP_WIN
// Used for MathML.
static constexpr uint64_t InnerHTML = ((uint64_t)0x1) << 17;
#endif
static constexpr uint64_t All = ~((uint64_t)0x0);
};

Просмотреть файл

@ -2502,6 +2502,20 @@ void LocalAccessible::BindToParent(LocalAccessible* aParent,
// for the next tick before the cache update is sent.
mDoc->QueueCacheUpdate(aParent, CacheDomain::Table);
}
#if defined(XP_WIN)
if (StaticPrefs::accessibility_cache_enabled_AtStartup() &&
aParent->HasOwnContent() && aParent->mContent->IsMathMLElement()) {
// For any change in a MathML subtree, update the innerHTML cache on the
// root math element.
for (LocalAccessible* acc = aParent; acc; acc = acc->LocalParent()) {
if (acc->HasOwnContent() &&
acc->mContent->IsMathMLElement(nsGkAtoms::math)) {
mDoc->QueueCacheUpdate(acc, CacheDomain::InnerHTML);
}
}
}
#endif // defined(XP_WIN)
}
// LocalAccessible protected
@ -3598,6 +3612,15 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
}
}
#if defined(XP_WIN)
if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() &&
mContent->IsMathMLElement(nsGkAtoms::math)) {
nsString innerHTML;
mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
fields->SetAttribute(nsGkAtoms::html, std::move(innerHTML));
}
#endif // defined(XP_WIN)
if (aUpdateType == CacheUpdateType::Initial) {
// Add fields which never change and thus only need to be included in the
// initial cache push.

Просмотреть файл

@ -408,6 +408,9 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
friend HyperTextAccessibleBase;
friend class xpcAccessible;
friend class CachedTableCellAccessible;
#ifdef XP_WIN
friend class sdnAccessible;
#endif
nsTArray<Derived*> mChildren;
DocAccessibleParent* mDoc;

Просмотреть файл

@ -21,6 +21,8 @@ skip-if =
[browser_caching_attributes.js]
[browser_caching_description.js]
[browser_caching_document_props.js]
[browser_caching_innerHTML.js]
skip-if = os != 'win'
[browser_caching_name.js]
skip-if = (os == "linux" && bits == 64) || (debug && os == "mac") || (debug && os == "win") #Bug 1388256
[browser_caching_relations.js]

Просмотреть файл

@ -0,0 +1,55 @@
/* 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/. */
"use strict";
/**
* Test caching of innerHTML on math elements for Windows clients.
*/
addAccessibleTask(
`
<p id="p">test</p>
<math id="math"><mfrac><mi>x</mi><mi>y</mi></mfrac></math>
`,
async function(browser, docAcc) {
if (!isCacheEnabled) {
// Stop the harness from complaining that this file is empty when run with
// the cache disabled.
todo(false, "Cache disabled for a cache only test");
return;
}
const p = findAccessibleChildByID(docAcc, "p");
let hasHtml;
try {
p.cache.getStringProperty("html");
hasHtml = true;
} catch (e) {
hasHtml = false;
}
ok(!hasHtml, "p doesn't have cached html");
const math = findAccessibleChildByID(docAcc, "math");
is(
math.cache.getStringProperty("html"),
"<mfrac><mi>x</mi><mi>y</mi></mfrac>",
"math cached html is correct"
);
info("Mutating math");
await invokeContentTask(browser, [], () => {
content.document.querySelectorAll("mi")[1].textContent = "z";
});
await untilCacheIs(
() => math.cache.getStringProperty("html"),
"<mfrac><mi>x</mi><mi>z</mi></mfrac>",
"math cached html is correct after mutation"
);
},
{
topLevel: true,
iframe: isCacheEnabled,
remoteIframe: isCacheEnabled,
}
);

Просмотреть файл

@ -862,8 +862,8 @@ MsaaAccessible::QueryInterface(REFIID iid, void** ppv) {
return E_NOINTERFACE;
}
*ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
} else if (IID_ISimpleDOMNode == iid && localAcc) {
if (!localAcc->HasOwnContent() && !localAcc->IsDoc()) {
} else if (IID_ISimpleDOMNode == iid) {
if (mAcc->IsDoc() || (localAcc && !localAcc->HasOwnContent())) {
return E_NOINTERFACE;
}

Просмотреть файл

@ -16,6 +16,7 @@ namespace mozilla {
namespace a11y {
inline DocAccessible* sdnAccessible::GetDocument() const {
MOZ_ASSERT(mNode);
return GetExistingDocAccessible(mNode->OwnerDoc());
}

Просмотреть файл

@ -85,6 +85,10 @@ sdnAccessible::get_nodeInfo(BSTR __RPC_FAR* aNodeName,
*aNodeType = 0;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
uint16_t nodeType = mNode->NodeType();
*aNodeType = static_cast<unsigned short>(nodeType);
@ -133,6 +137,10 @@ sdnAccessible::get_attributes(unsigned short aMaxAttribs,
*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;
@ -167,6 +175,13 @@ sdnAccessible::get_attributesForNames(unsigned short aMaxAttribs,
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;
@ -205,6 +220,10 @@ sdnAccessible::get_computedStyle(
return E_INVALIDARG;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
*aNumStyleProperties = 0;
@ -248,6 +267,10 @@ sdnAccessible::get_computedStyleForProperties(
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;
@ -271,10 +294,16 @@ sdnAccessible::get_computedStyleForProperties(
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP
sdnAccessible::scrollTo(boolean aScrollTopLeft) {
DocAccessible* document = GetDocument();
if (!document) // that's IsDefunct check
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
@ -293,6 +322,10 @@ sdnAccessible::get_parentNode(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*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) {
@ -309,6 +342,10 @@ sdnAccessible::get_firstChild(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*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) {
@ -325,6 +362,10 @@ sdnAccessible::get_lastChild(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*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) {
@ -341,6 +382,10 @@ sdnAccessible::get_previousSibling(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*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) {
@ -357,6 +402,10 @@ sdnAccessible::get_nextSibling(ISimpleDOMNode __RPC_FAR* __RPC_FAR* aNode) {
*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) {
@ -374,6 +423,10 @@ sdnAccessible::get_childAt(unsigned aChildIndex,
*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) {
@ -391,10 +444,21 @@ sdnAccessible::get_innerHTML(BSTR __RPC_FAR* aInnerHTML) {
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode->IsElement()) return S_FALSE;
nsAutoString innerHTML;
mNode->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
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());
@ -422,6 +486,10 @@ sdnAccessible::get_language(BSTR __RPC_FAR* aLanguage) {
*aLanguage = nullptr;
if (IsDefunct()) return CO_E_OBJNOTCONNECTED;
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return E_NOTIMPL;
}
nsAutoString language;
if (mNode->IsContent())

Просмотреть файл

@ -25,18 +25,32 @@ class sdnAccessible final : public ISimpleDOMNode {
if (!mNode) MOZ_CRASH();
}
explicit sdnAccessible(NotNull<MsaaAccessible*> aMsaa)
: mNode(aMsaa->LocalAcc()->GetNode()), mMsaa(aMsaa) {}
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 { return !GetDocument(); }
bool IsDefunct() const {
if (mMsaa && !mMsaa->Acc()) {
return true;
}
if (!mNode) {
MOZ_ASSERT(mMsaa && mMsaa->Acc()->IsRemote());
return false;
}
return !GetDocument();
}
/**
* Return a document accessible it belongs to if any.
* Return a local document accessible it belongs to if any.
*/
DocAccessible* GetDocument() const;
@ -122,6 +136,8 @@ class sdnAccessible final : public ISimpleDOMNode {
/* [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;