2016-08-19 22:16:42 +03:00
|
|
|
/* -*- 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 "DocAccessible.h"
|
|
|
|
#include "mozilla/a11y/DocAccessibleParent.h"
|
|
|
|
#include "mozilla/a11y/DocManager.h"
|
|
|
|
#include "mozilla/a11y/Platform.h"
|
2021-02-20 02:14:33 +03:00
|
|
|
#include "mozilla/a11y/RemoteAccessibleBase.h"
|
|
|
|
#include "mozilla/a11y/RemoteAccessible.h"
|
2016-08-19 22:16:42 +03:00
|
|
|
#include "mozilla/a11y/Role.h"
|
|
|
|
#include "mozilla/dom/Element.h"
|
2019-04-10 00:38:15 +03:00
|
|
|
#include "mozilla/dom/BrowserParent.h"
|
2021-10-05 23:44:46 +03:00
|
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
|
|
#include "mozilla/dom/DocumentInlines.h"
|
2016-08-23 07:09:32 +03:00
|
|
|
#include "mozilla/Unused.h"
|
2021-10-06 07:53:53 +03:00
|
|
|
#include "nsAccUtils.h"
|
2016-08-19 22:16:42 +03:00
|
|
|
#include "RelationType.h"
|
2020-05-01 07:28:35 +03:00
|
|
|
#include "xpcAccessibleDocument.h"
|
2016-08-19 22:16:42 +03:00
|
|
|
|
2021-09-29 00:55:41 +03:00
|
|
|
#ifdef A11Y_LOG
|
|
|
|
# include "Logging.h"
|
|
|
|
# define VERIFY_CACHE(domain) \
|
|
|
|
if (logging::IsEnabled(logging::eCache)) { \
|
|
|
|
Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
# define VERIFY_CACHE(domain) \
|
|
|
|
do { \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2016-08-19 22:16:42 +03:00
|
|
|
namespace mozilla {
|
|
|
|
namespace a11y {
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
void RemoteAccessibleBase<Derived>::Shutdown() {
|
2016-08-19 22:16:42 +03:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
|
|
|
|
xpcAccessibleDocument* xpcDoc =
|
|
|
|
GetAccService()->GetCachedXPCDocument(Document());
|
|
|
|
if (xpcDoc) {
|
|
|
|
xpcDoc->NotifyOfShutdown(static_cast<Derived*>(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX Ideally this wouldn't be necessary, but it seems OuterDoc accessibles
|
|
|
|
// can be destroyed before the doc they own.
|
2017-02-07 23:56:55 +03:00
|
|
|
uint32_t childCount = mChildren.Length();
|
2021-05-12 02:17:08 +03:00
|
|
|
if (!IsOuterDoc()) {
|
2016-08-19 22:16:42 +03:00
|
|
|
for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
|
|
|
|
} else {
|
2017-02-07 23:56:55 +03:00
|
|
|
if (childCount > 1) {
|
|
|
|
MOZ_CRASH("outer doc has too many documents!");
|
|
|
|
} else if (childCount == 1) {
|
|
|
|
mChildren[0]->AsDoc()->Unbind();
|
|
|
|
}
|
2016-08-19 22:16:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
mChildren.Clear();
|
|
|
|
ProxyDestroyed(static_cast<Derived*>(this));
|
|
|
|
mDoc->RemoveAccessible(static_cast<Derived*>(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
void RemoteAccessibleBase<Derived>::SetChildDoc(
|
|
|
|
DocAccessibleParent* aChildDoc) {
|
2016-12-03 00:50:00 +03:00
|
|
|
MOZ_ASSERT(aChildDoc);
|
2017-04-07 23:26:45 +03:00
|
|
|
MOZ_ASSERT(mChildren.Length() == 0);
|
|
|
|
mChildren.AppendElement(aChildDoc);
|
2016-12-03 00:50:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
void RemoteAccessibleBase<Derived>::ClearChildDoc(
|
2016-12-03 00:50:00 +03:00
|
|
|
DocAccessibleParent* aChildDoc) {
|
|
|
|
MOZ_ASSERT(aChildDoc);
|
|
|
|
// This is possible if we're replacing one document with another: Doc 1
|
|
|
|
// has not had a chance to remove itself, but was already replaced by Doc 2
|
|
|
|
// in SetChildDoc(). This could result in two subsequent calls to
|
|
|
|
// ClearChildDoc() even though mChildren.Length() == 1.
|
|
|
|
MOZ_ASSERT(mChildren.Length() <= 1);
|
2017-02-07 23:56:55 +03:00
|
|
|
mChildren.RemoveElement(aChildDoc);
|
2016-08-19 22:16:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
uint32_t RemoteAccessibleBase<Derived>::EmbeddedChildCount() const {
|
2016-08-19 22:16:42 +03:00
|
|
|
size_t count = 0, kids = mChildren.Length();
|
|
|
|
for (size_t i = 0; i < kids; i++) {
|
|
|
|
if (mChildren[i]->IsEmbeddedObject()) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
int32_t RemoteAccessibleBase<Derived>::IndexOfEmbeddedChild(
|
2016-08-19 22:16:42 +03:00
|
|
|
const Derived* aChild) {
|
|
|
|
size_t index = 0, kids = mChildren.Length();
|
|
|
|
for (size_t i = 0; i < kids; i++) {
|
|
|
|
if (mChildren[i]->IsEmbeddedObject()) {
|
|
|
|
if (mChildren[i] == aChild) {
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
Derived* RemoteAccessibleBase<Derived>::EmbeddedChildAt(size_t aChildIdx) {
|
2016-08-19 22:16:42 +03:00
|
|
|
size_t index = 0, kids = mChildren.Length();
|
|
|
|
for (size_t i = 0; i < kids; i++) {
|
|
|
|
if (!mChildren[i]->IsEmbeddedObject()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index == aChildIdx) {
|
|
|
|
return mChildren[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
LocalAccessible* RemoteAccessibleBase<Derived>::OuterDocOfRemoteBrowser()
|
|
|
|
const {
|
2019-04-10 00:38:15 +03:00
|
|
|
auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager());
|
2016-08-19 22:16:42 +03:00
|
|
|
dom::Element* frame = tab->GetOwnerElement();
|
|
|
|
NS_ASSERTION(frame, "why isn't the tab in a frame!");
|
|
|
|
if (!frame) return nullptr;
|
|
|
|
|
|
|
|
DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
|
|
|
|
|
|
|
|
return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
|
|
|
|
}
|
|
|
|
|
2017-02-13 19:56:53 +03:00
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
void RemoteAccessibleBase<Derived>::SetParent(Derived* aParent) {
|
2017-02-13 19:56:53 +03:00
|
|
|
MOZ_ASSERT(IsDoc(), "we should only reparent documents");
|
|
|
|
if (!aParent) {
|
|
|
|
mParent = kNoParent;
|
|
|
|
} else {
|
|
|
|
MOZ_ASSERT(!aParent->IsDoc());
|
|
|
|
mParent = aParent->ID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
2021-02-20 02:14:33 +03:00
|
|
|
Derived* RemoteAccessibleBase<Derived>::RemoteParent() const {
|
2017-02-13 19:56:53 +03:00
|
|
|
if (mParent == kNoParent) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we are not a document then are parent is another proxy in the same
|
|
|
|
// document. That means we can just ask our document for the proxy with our
|
|
|
|
// parent id.
|
|
|
|
if (!IsDoc()) {
|
|
|
|
return Document()->GetAccessible(mParent);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are a top level document then our parent is not a proxy.
|
|
|
|
if (AsDoc()->IsTopLevel()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally if we are a non top level document then our parent id is for a
|
|
|
|
// proxy in our parent document so get the proxy from there.
|
|
|
|
DocAccessibleParent* parentDoc = AsDoc()->ParentDoc();
|
|
|
|
MOZ_ASSERT(parentDoc);
|
|
|
|
MOZ_ASSERT(mParent);
|
|
|
|
return parentDoc->GetAccessible(mParent);
|
|
|
|
}
|
|
|
|
|
2021-08-19 22:57:19 +03:00
|
|
|
template <class Derived>
|
|
|
|
ENameValueFlag RemoteAccessibleBase<Derived>::Name(nsString& aName) const {
|
2021-10-06 07:53:52 +03:00
|
|
|
if (mCachedFields) {
|
|
|
|
if (IsText()) {
|
|
|
|
mCachedFields->GetAttribute(nsGkAtoms::text, aName);
|
|
|
|
return eNameOK;
|
|
|
|
}
|
|
|
|
if (mCachedFields->GetAttribute(nsGkAtoms::name, aName)) {
|
|
|
|
auto nameFlag =
|
|
|
|
mCachedFields->GetAttribute<int32_t>(nsGkAtoms::explicit_name);
|
|
|
|
VERIFY_CACHE(CacheDomain::NameAndDescription);
|
|
|
|
return nameFlag ? static_cast<ENameValueFlag>(*nameFlag) : eNameOK;
|
|
|
|
}
|
2021-08-19 22:57:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return eNameOK;
|
|
|
|
}
|
|
|
|
|
2021-09-02 21:06:52 +03:00
|
|
|
template <class Derived>
|
|
|
|
void RemoteAccessibleBase<Derived>::Description(nsString& aDescription) const {
|
|
|
|
if (mCachedFields) {
|
|
|
|
mCachedFields->GetAttribute(nsGkAtoms::description, aDescription);
|
2021-09-29 00:55:41 +03:00
|
|
|
VERIFY_CACHE(CacheDomain::NameAndDescription);
|
2021-09-02 21:06:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 22:40:05 +03:00
|
|
|
template <class Derived>
|
|
|
|
double RemoteAccessibleBase<Derived>::CurValue() const {
|
|
|
|
if (auto value = mCachedFields->GetAttribute<double>(nsGkAtoms::value)) {
|
2021-09-29 00:55:41 +03:00
|
|
|
VERIFY_CACHE(CacheDomain::Value);
|
2021-08-26 22:40:05 +03:00
|
|
|
return *value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return UnspecifiedNaN<double>();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
|
|
|
double RemoteAccessibleBase<Derived>::MinValue() const {
|
|
|
|
if (auto min = mCachedFields->GetAttribute<double>(nsGkAtoms::min)) {
|
2021-09-29 00:55:41 +03:00
|
|
|
VERIFY_CACHE(CacheDomain::Value);
|
2021-08-26 22:40:05 +03:00
|
|
|
return *min;
|
|
|
|
}
|
|
|
|
|
|
|
|
return UnspecifiedNaN<double>();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
|
|
|
double RemoteAccessibleBase<Derived>::MaxValue() const {
|
|
|
|
if (auto max = mCachedFields->GetAttribute<double>(nsGkAtoms::max)) {
|
2021-09-29 00:55:41 +03:00
|
|
|
VERIFY_CACHE(CacheDomain::Value);
|
2021-08-26 22:40:05 +03:00
|
|
|
return *max;
|
|
|
|
}
|
|
|
|
|
|
|
|
return UnspecifiedNaN<double>();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
|
|
|
double RemoteAccessibleBase<Derived>::Step() const {
|
|
|
|
if (auto step = mCachedFields->GetAttribute<double>(nsGkAtoms::step)) {
|
2021-09-29 00:55:41 +03:00
|
|
|
VERIFY_CACHE(CacheDomain::Value);
|
2021-08-26 22:40:05 +03:00
|
|
|
return *step;
|
|
|
|
}
|
|
|
|
|
|
|
|
return UnspecifiedNaN<double>();
|
|
|
|
}
|
|
|
|
|
2021-10-05 23:44:46 +03:00
|
|
|
template <class Derived>
|
|
|
|
Maybe<nsRect> RemoteAccessibleBase<Derived>::RetrieveCachedBounds() const {
|
|
|
|
Maybe<const nsTArray<int32_t>&> maybeArray =
|
|
|
|
mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::relativeBounds);
|
|
|
|
if (maybeArray) {
|
|
|
|
const nsTArray<int32_t>& relativeBoundsArr = *maybeArray;
|
|
|
|
MOZ_ASSERT(relativeBoundsArr.Length() == 4,
|
|
|
|
"Incorrectly sized bounds array");
|
|
|
|
nsRect relativeBoundsRect(relativeBoundsArr[0], relativeBoundsArr[1],
|
|
|
|
relativeBoundsArr[2], relativeBoundsArr[3]);
|
|
|
|
return Some(relativeBoundsRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Nothing();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class Derived>
|
|
|
|
nsIntRect RemoteAccessibleBase<Derived>::Bounds() const {
|
|
|
|
if (mCachedFields) {
|
|
|
|
Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
|
|
|
|
if (maybeBounds) {
|
|
|
|
nsRect bounds = *maybeBounds;
|
|
|
|
nsIntRect devPxBounds;
|
|
|
|
dom::CanonicalBrowsingContext* cbc =
|
|
|
|
static_cast<dom::BrowserParent*>(mDoc->Manager())
|
|
|
|
->GetBrowsingContext()
|
|
|
|
->Top();
|
|
|
|
dom::BrowserParent* bp = cbc->GetBrowserParent();
|
|
|
|
nsPresContext* presContext =
|
|
|
|
bp->GetOwnerElement()->OwnerDoc()->GetPresContext();
|
|
|
|
|
|
|
|
const Accessible* acc = this;
|
|
|
|
while (acc) {
|
|
|
|
if (LocalAccessible* localAcc =
|
|
|
|
const_cast<Accessible*>(acc)->AsLocal()) {
|
|
|
|
// LocalAccessible::Bounds returns screen-relative bounds in
|
|
|
|
// dev pixels.
|
|
|
|
nsIntRect localBounds = localAcc->Bounds();
|
|
|
|
|
|
|
|
// Convert our existing `bounds` rect from app units to dev pixels
|
|
|
|
devPxBounds =
|
|
|
|
bounds.ToNearestPixels(presContext->AppUnitsPerDevPixel());
|
|
|
|
|
|
|
|
// We factor in our zoom level before offsetting by
|
|
|
|
// `localBounds`, which has already taken zoom into account.
|
|
|
|
devPxBounds.ScaleRoundOut(cbc->GetFullZoom());
|
|
|
|
|
|
|
|
// The root document will always have an APZ resolution of 1,
|
|
|
|
// so we don't factor in its scale here. We also don't scale
|
|
|
|
// by GetFullZoom because LocalAccessible::Bounds already does
|
|
|
|
// that.
|
|
|
|
devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
|
|
|
|
// Verify that remoteAcc is not `this`, since `bounds` was
|
|
|
|
// initialised to include this->RetrieveCachedBounds()
|
|
|
|
Maybe<nsRect> maybeRemoteBounds =
|
|
|
|
(remoteAcc == this) ? Nothing() : remoteAcc->RetrieveCachedBounds();
|
|
|
|
|
|
|
|
if (maybeRemoteBounds) {
|
|
|
|
// We need to take into account a non-1 resolution set on the
|
|
|
|
// presshell. This happens with async pinch zooming, among other
|
|
|
|
// things. We can't reliably query this value in the parent process,
|
|
|
|
// so we retrieve it from the document's cache.
|
|
|
|
Maybe<float> res;
|
|
|
|
if (remoteAcc->IsDoc()) {
|
|
|
|
// Apply the document's resolution to the bounds we've gathered
|
|
|
|
// thus far. We do this before applying the document's offset
|
|
|
|
// because document accs should not have their bounds scaled by
|
|
|
|
// their own resolution. They should be scaled by the resolution
|
|
|
|
// of their containing document (if any). We also skip this in the
|
|
|
|
// case that remoteAcc == this, since that implies `bounds` should
|
|
|
|
// be scaled relative to its parent doc.
|
|
|
|
res = remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
|
|
|
|
nsGkAtoms::resolution);
|
|
|
|
bounds.ScaleRoundOut(res.valueOr(1.0f));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regardless of whether this is a doc, we should offset `bounds`
|
|
|
|
// by the bounds retrieved here. This is how we build screen
|
|
|
|
// coordinates from relative coordinates.
|
|
|
|
nsRect remoteBounds = *maybeRemoteBounds;
|
|
|
|
bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
|
|
|
|
}
|
|
|
|
|
|
|
|
acc = acc->Parent();
|
|
|
|
}
|
|
|
|
|
|
|
|
PresShell* presShell = presContext->PresShell();
|
|
|
|
|
|
|
|
// Our relative bounds are pulled from the coordinate space of the layout
|
|
|
|
// viewport, but we need them to be in the coordinate space of the visual
|
|
|
|
// viewport. We calculate the difference and translate our bounds here.
|
|
|
|
nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
|
|
|
|
presShell->GetLayoutViewportOffset();
|
|
|
|
devPxBounds.MoveBy(-(
|
|
|
|
viewportOffset.ToNearestPixels(presContext->AppUnitsPerDevPixel())));
|
|
|
|
|
|
|
|
return devPxBounds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nsIntRect();
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:53:53 +03:00
|
|
|
template <class Derived>
|
|
|
|
void RemoteAccessibleBase<Derived>::AppendTextTo(nsAString& aText,
|
|
|
|
uint32_t aStartOffset,
|
|
|
|
uint32_t aLength) {
|
|
|
|
if (IsText()) {
|
|
|
|
auto text = mCachedFields->GetAttribute<nsString>(nsGkAtoms::text);
|
|
|
|
if (text) {
|
|
|
|
aText.Append(Substring(*text, aStartOffset, aLength));
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aStartOffset != 0 || aLength == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsHTMLBr()) {
|
|
|
|
aText += kForcedNewLineChar;
|
|
|
|
} else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
|
|
|
|
// Expose the embedded object accessible as imaginary embedded object
|
|
|
|
// character if its parent hypertext accessible doesn't expose children to
|
|
|
|
// AT.
|
|
|
|
aText += kImaginaryEmbeddedObjectChar;
|
|
|
|
} else {
|
|
|
|
aText += kEmbeddedObjectChar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:53:53 +03:00
|
|
|
template <class Derived>
|
|
|
|
uint32_t RemoteAccessibleBase<Derived>::GetCachedTextLength() {
|
|
|
|
MOZ_ASSERT(IsText());
|
|
|
|
auto text = mCachedFields->GetAttribute<nsString>(nsGkAtoms::text);
|
|
|
|
if (!text) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return text->Length();
|
|
|
|
}
|
|
|
|
|
2021-10-06 07:53:54 +03:00
|
|
|
template <class Derived>
|
|
|
|
Maybe<const nsTArray<int32_t>&>
|
|
|
|
RemoteAccessibleBase<Derived>::GetCachedTextLines() {
|
|
|
|
MOZ_ASSERT(IsText());
|
|
|
|
return mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::line);
|
|
|
|
}
|
|
|
|
|
2021-10-08 04:57:46 +03:00
|
|
|
template <class Derived>
|
|
|
|
void RemoteAccessibleBase<Derived>::DOMNodeID(nsString& aID) const {
|
|
|
|
if (mCachedFields) {
|
|
|
|
mCachedFields->GetAttribute(nsGkAtoms::id, aID);
|
|
|
|
VERIFY_CACHE(CacheDomain::DOMNodeID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-20 02:14:33 +03:00
|
|
|
template class RemoteAccessibleBase<RemoteAccessible>;
|
2016-08-19 22:16:42 +03:00
|
|
|
|
|
|
|
} // namespace a11y
|
|
|
|
} // namespace mozilla
|