2020-08-26 00:40:32 +03:00
|
|
|
/* clang-format off */
|
2020-07-03 00:35:05 +03:00
|
|
|
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
2020-08-26 00:40:32 +03:00
|
|
|
/* clang-format on */
|
2020-07-03 00:35:05 +03:00
|
|
|
/* 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 "DocAccessibleParent.h"
|
2021-06-11 02:07:05 +03:00
|
|
|
#include "AccAttributes.h"
|
2020-07-03 00:57:55 +03:00
|
|
|
#include "nsCocoaUtils.h"
|
2020-07-03 00:35:05 +03:00
|
|
|
|
2020-08-14 22:33:02 +03:00
|
|
|
#include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
|
|
|
|
|
2020-07-03 00:35:05 +03:00
|
|
|
#import "GeckoTextMarker.h"
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
|
|
CFTypeID AXTextMarkerGetTypeID();
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRef AXTextMarkerCreate(CFAllocatorRef allocator, const UInt8* bytes,
|
|
|
|
CFIndex length);
|
2020-07-03 00:35:05 +03:00
|
|
|
|
|
|
|
const UInt8* AXTextMarkerGetBytePtr(AXTextMarkerRef text_marker);
|
|
|
|
|
|
|
|
size_t AXTextMarkerGetLength(AXTextMarkerRef text_marker);
|
|
|
|
|
|
|
|
CFTypeID AXTextMarkerRangeGetTypeID();
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRangeRef AXTextMarkerRangeCreate(CFAllocatorRef allocator,
|
|
|
|
AXTextMarkerRef start_marker,
|
2020-07-03 00:35:05 +03:00
|
|
|
AXTextMarkerRef end_marker);
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRef AXTextMarkerRangeCopyStartMarker(
|
|
|
|
AXTextMarkerRangeRef text_marker_range);
|
2020-07-03 00:35:05 +03:00
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRef AXTextMarkerRangeCopyEndMarker(
|
|
|
|
AXTextMarkerRangeRef text_marker_range);
|
2020-07-03 00:35:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace a11y {
|
|
|
|
|
|
|
|
struct OpaqueGeckoTextMarker {
|
2020-10-07 02:31:32 +03:00
|
|
|
OpaqueGeckoTextMarker(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
|
|
|
|
: mDoc(aDoc), mID(aID), mOffset(aOffset) {}
|
2020-07-03 00:35:05 +03:00
|
|
|
OpaqueGeckoTextMarker() {}
|
2020-10-07 02:31:32 +03:00
|
|
|
uintptr_t mDoc;
|
2020-07-03 00:35:05 +03:00
|
|
|
uintptr_t mID;
|
|
|
|
int32_t mOffset;
|
|
|
|
};
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
static bool DocumentExists(Accessible* aDoc, uintptr_t aDocPtr) {
|
|
|
|
if (reinterpret_cast<uintptr_t>(aDoc) == aDocPtr) {
|
2020-10-07 02:31:32 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
if (aDoc->IsLocal()) {
|
|
|
|
DocAccessible* docAcc = aDoc->AsLocal()->AsDoc();
|
2020-10-07 02:31:32 +03:00
|
|
|
uint32_t docCount = docAcc->ChildDocumentCount();
|
|
|
|
for (uint32_t i = 0; i < docCount; i++) {
|
|
|
|
if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* docProxy = aDoc->AsRemote()->AsDoc();
|
2020-10-07 02:31:32 +03:00
|
|
|
size_t docCount = docProxy->ChildDocCount();
|
|
|
|
for (uint32_t i = 0; i < docCount; i++) {
|
|
|
|
if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-03 00:35:05 +03:00
|
|
|
// GeckoTextMarker
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
GeckoTextMarker::GeckoTextMarker(Accessible* aDoc,
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRef aTextMarker) {
|
2021-07-22 20:58:49 +03:00
|
|
|
MOZ_ASSERT(aDoc);
|
2020-07-03 00:35:05 +03:00
|
|
|
OpaqueGeckoTextMarker opaqueMarker;
|
2021-01-23 00:12:56 +03:00
|
|
|
if (aTextMarker &&
|
|
|
|
AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
|
2020-08-26 00:40:32 +03:00
|
|
|
memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker),
|
|
|
|
sizeof(OpaqueGeckoTextMarker));
|
2020-10-07 02:31:32 +03:00
|
|
|
if (DocumentExists(aDoc, opaqueMarker.mDoc)) {
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* doc = reinterpret_cast<Accessible*>(opaqueMarker.mDoc);
|
|
|
|
if (doc->IsRemote()) {
|
|
|
|
mContainer = doc->AsRemote()->AsDoc()->GetAccessible(opaqueMarker.mID);
|
2020-10-07 02:31:32 +03:00
|
|
|
} else {
|
2021-07-22 20:58:49 +03:00
|
|
|
mContainer = doc->AsLocal()->AsDoc()->GetAccessibleByUniqueID(
|
2020-10-07 02:31:32 +03:00
|
|
|
reinterpret_cast<void*>(opaqueMarker.mID));
|
|
|
|
}
|
2020-07-03 00:35:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
mOffset = opaqueMarker.mOffset;
|
2021-07-22 20:58:49 +03:00
|
|
|
} else {
|
|
|
|
mContainer = nullptr;
|
|
|
|
mOffset = 0;
|
2020-07-03 00:35:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
|
2020-08-27 23:06:51 +03:00
|
|
|
int32_t aIndex) {
|
2021-07-22 20:58:49 +03:00
|
|
|
if (aRoot->IsRemote()) {
|
2020-08-27 23:06:51 +03:00
|
|
|
int32_t offset = 0;
|
|
|
|
uint64_t containerID = 0;
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* ipcDoc = aRoot->AsRemote()->Document();
|
2020-08-27 23:06:51 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendOffsetAtIndex(
|
2021-07-22 20:58:49 +03:00
|
|
|
aRoot->AsRemote()->ID(), aIndex, &containerID, &offset);
|
2021-02-20 02:14:33 +03:00
|
|
|
RemoteAccessible* container = ipcDoc->GetAccessible(containerID);
|
2020-08-27 23:06:51 +03:00
|
|
|
return GeckoTextMarker(container, offset);
|
|
|
|
} else if (auto htWrap = static_cast<HyperTextAccessibleWrap*>(
|
2021-07-22 20:58:49 +03:00
|
|
|
aRoot->AsLocal()->AsHyperText())) {
|
2020-08-27 23:06:51 +03:00
|
|
|
int32_t offset = 0;
|
|
|
|
HyperTextAccessible* container = nullptr;
|
|
|
|
htWrap->OffsetAtIndex(aIndex, &container, &offset);
|
|
|
|
return GeckoTextMarker(container, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
return GeckoTextMarker();
|
|
|
|
}
|
|
|
|
|
2020-07-03 00:35:05 +03:00
|
|
|
id GeckoTextMarker::CreateAXTextMarker() {
|
2020-10-30 07:41:20 +03:00
|
|
|
if (!IsValid()) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* doc;
|
|
|
|
if (mContainer->IsRemote()) {
|
|
|
|
doc = mContainer->AsRemote()->Document();
|
2020-10-07 02:31:32 +03:00
|
|
|
} else {
|
2021-07-22 20:58:49 +03:00
|
|
|
doc = mContainer->AsLocal()->Document();
|
2020-10-07 02:31:32 +03:00
|
|
|
}
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
uintptr_t identifier =
|
2021-07-22 20:58:49 +03:00
|
|
|
mContainer->IsRemote()
|
|
|
|
? mContainer->AsRemote()->ID()
|
|
|
|
: reinterpret_cast<uintptr_t>(mContainer->AsLocal()->UniqueID());
|
2020-10-07 02:31:32 +03:00
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
OpaqueGeckoTextMarker opaqueMarker(reinterpret_cast<uintptr_t>(doc),
|
|
|
|
identifier, mOffset);
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
|
|
|
|
kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&opaqueMarker),
|
|
|
|
sizeof(OpaqueGeckoTextMarker));
|
2020-07-03 00:35:05 +03:00
|
|
|
|
|
|
|
return [static_cast<id>(cf_text_marker) autorelease];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GeckoTextMarker::operator<(const GeckoTextMarker& aPoint) const {
|
|
|
|
if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
|
|
|
|
|
|
|
|
// Build the chain of parents
|
2021-07-22 20:58:49 +03:00
|
|
|
AutoTArray<Accessible*, 30> parents1, parents2;
|
|
|
|
Accessible* p1 = mContainer;
|
|
|
|
while (p1) {
|
2020-07-03 00:35:05 +03:00
|
|
|
parents1.AppendElement(p1);
|
2021-07-22 20:58:49 +03:00
|
|
|
p1 = p1->Parent();
|
2020-09-09 23:43:35 +03:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* p2 = aPoint.mContainer;
|
|
|
|
while (p2) {
|
2020-07-03 00:35:05 +03:00
|
|
|
parents2.AppendElement(p2);
|
2021-07-22 20:58:49 +03:00
|
|
|
p2 = p2->Parent();
|
2020-09-09 23:43:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// An empty chain of parents means one of the containers was null.
|
|
|
|
MOZ_ASSERT(parents1.Length() != 0 && parents2.Length() != 0,
|
|
|
|
"have empty chain of parents!");
|
2020-07-03 00:35:05 +03:00
|
|
|
|
|
|
|
// Find where the parent chain differs
|
|
|
|
uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
|
|
|
|
for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* child1 = parents1.ElementAt(--pos1);
|
|
|
|
Accessible* child2 = parents2.ElementAt(--pos2);
|
2020-07-03 00:35:05 +03:00
|
|
|
if (child1 != child2) {
|
2021-07-22 20:58:49 +03:00
|
|
|
return child1->IndexInParent() < child2->IndexInParent();
|
2020-07-03 00:35:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-06 20:24:19 +03:00
|
|
|
if (pos1 != 0) {
|
|
|
|
// If parents1 is a superset of parents2 then mContainer is a
|
|
|
|
// descendant of aPoint.mContainer. The next element down in parents1
|
|
|
|
// is mContainer's ancestor that is the child of aPoint.mContainer.
|
|
|
|
// We compare its end offset in aPoint.mContainer with aPoint.mOffset.
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* child = parents1.ElementAt(pos1 - 1);
|
|
|
|
MOZ_ASSERT(child->Parent() == aPoint.mContainer);
|
2020-10-06 20:24:19 +03:00
|
|
|
bool unused;
|
2021-07-22 20:58:49 +03:00
|
|
|
uint32_t endOffset = child->IsRemote()
|
|
|
|
? child->AsRemote()->EndOffset(&unused)
|
|
|
|
: child->AsLocal()->EndOffset();
|
2020-10-06 20:24:19 +03:00
|
|
|
return endOffset < static_cast<uint32_t>(aPoint.mOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos2 != 0) {
|
|
|
|
// If parents2 is a superset of parents1 then aPoint.mContainer is a
|
|
|
|
// descendant of mContainer. The next element down in parents2
|
|
|
|
// is aPoint.mContainer's ancestor that is the child of mContainer.
|
|
|
|
// We compare its start offset in mContainer with mOffset.
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* child = parents2.ElementAt(pos2 - 1);
|
|
|
|
MOZ_ASSERT(child->Parent() == mContainer);
|
2020-10-06 20:24:19 +03:00
|
|
|
bool unused;
|
2021-07-22 20:58:49 +03:00
|
|
|
uint32_t startOffset = child->IsRemote()
|
|
|
|
? child->AsRemote()->StartOffset(&unused)
|
|
|
|
: child->AsLocal()->StartOffset();
|
2021-01-15 22:18:46 +03:00
|
|
|
return static_cast<uint32_t>(mOffset) <= startOffset;
|
2020-10-06 20:24:19 +03:00
|
|
|
}
|
|
|
|
|
2020-07-03 00:35:05 +03:00
|
|
|
MOZ_ASSERT_UNREACHABLE("Broken tree?!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-22 02:02:57 +03:00
|
|
|
bool GeckoTextMarker::IsEditableRoot() {
|
2021-07-22 20:58:49 +03:00
|
|
|
uint64_t state = mContainer->IsRemote() ? mContainer->AsRemote()->State()
|
|
|
|
: mContainer->AsLocal()->State();
|
2020-07-22 02:02:57 +03:00
|
|
|
if ((state & states::EDITABLE) == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* parent = mContainer->Parent();
|
|
|
|
if (!parent) {
|
2020-08-26 00:40:32 +03:00
|
|
|
// Not sure when this can happen, but it would technically be an editable
|
|
|
|
// root.
|
2020-07-22 02:02:57 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
state = parent->IsRemote() ? parent->AsRemote()->State()
|
|
|
|
: parent->AsLocal()->State();
|
2020-07-22 02:02:57 +03:00
|
|
|
|
|
|
|
return (state & states::EDITABLE) == 0;
|
|
|
|
}
|
|
|
|
|
2020-08-14 22:33:02 +03:00
|
|
|
bool GeckoTextMarker::Next() {
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mContainer->IsRemote()) {
|
2020-08-14 22:33:02 +03:00
|
|
|
int32_t nextOffset = 0;
|
|
|
|
uint64_t nextContainerID = 0;
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
|
2020-08-26 00:40:32 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendNextClusterAt(
|
2021-07-22 20:58:49 +03:00
|
|
|
mContainer->AsRemote()->ID(), mOffset, &nextContainerID, &nextOffset);
|
2021-02-20 02:14:33 +03:00
|
|
|
RemoteAccessible* nextContainer = ipcDoc->GetAccessible(nextContainerID);
|
2021-07-22 20:58:49 +03:00
|
|
|
bool moved =
|
|
|
|
nextContainer != mContainer->AsRemote() || nextOffset != mOffset;
|
2020-08-14 22:33:02 +03:00
|
|
|
mContainer = nextContainer;
|
|
|
|
mOffset = nextOffset;
|
|
|
|
return moved;
|
|
|
|
} else if (auto htWrap = ContainerAsHyperTextWrap()) {
|
|
|
|
HyperTextAccessible* nextContainer = nullptr;
|
|
|
|
int32_t nextOffset = 0;
|
|
|
|
htWrap->NextClusterAt(mOffset, &nextContainer, &nextOffset);
|
|
|
|
bool moved = nextContainer != htWrap || nextOffset != mOffset;
|
|
|
|
mContainer = nextContainer;
|
|
|
|
mOffset = nextOffset;
|
|
|
|
return moved;
|
2020-07-21 01:53:04 +03:00
|
|
|
}
|
|
|
|
|
2020-08-14 22:33:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
2020-07-21 01:53:04 +03:00
|
|
|
|
2020-08-14 22:33:02 +03:00
|
|
|
bool GeckoTextMarker::Previous() {
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mContainer->IsRemote()) {
|
2020-08-14 22:33:02 +03:00
|
|
|
int32_t prevOffset = 0;
|
|
|
|
uint64_t prevContainerID = 0;
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
|
2020-08-14 22:33:02 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendPreviousClusterAt(
|
2021-07-22 20:58:49 +03:00
|
|
|
mContainer->AsRemote()->ID(), mOffset, &prevContainerID, &prevOffset);
|
2021-02-20 02:14:33 +03:00
|
|
|
RemoteAccessible* prevContainer = ipcDoc->GetAccessible(prevContainerID);
|
2021-07-22 20:58:49 +03:00
|
|
|
bool moved =
|
|
|
|
prevContainer != mContainer->AsRemote() || prevOffset != mOffset;
|
2020-08-14 22:33:02 +03:00
|
|
|
mContainer = prevContainer;
|
|
|
|
mOffset = prevOffset;
|
|
|
|
return moved;
|
|
|
|
} else if (auto htWrap = ContainerAsHyperTextWrap()) {
|
|
|
|
HyperTextAccessible* prevContainer = nullptr;
|
|
|
|
int32_t prevOffset = 0;
|
|
|
|
htWrap->PreviousClusterAt(mOffset, &prevContainer, &prevOffset);
|
|
|
|
bool moved = prevContainer != htWrap || prevOffset != mOffset;
|
|
|
|
mContainer = prevContainer;
|
|
|
|
mOffset = prevOffset;
|
|
|
|
return moved;
|
2020-07-21 01:53:04 +03:00
|
|
|
}
|
2020-08-14 22:33:02 +03:00
|
|
|
|
|
|
|
return false;
|
2020-07-21 01:53:04 +03:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
static uint32_t CharacterCount(Accessible* aContainer) {
|
|
|
|
if (aContainer->IsRemote()) {
|
|
|
|
return aContainer->AsRemote()->CharacterCount();
|
2020-07-21 01:53:04 +03:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
if (aContainer->AsLocal()->IsHyperText()) {
|
|
|
|
return aContainer->AsLocal()->AsHyperText()->CharacterCount();
|
2020-07-21 01:53:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-09-24 19:04:41 +03:00
|
|
|
GeckoTextMarkerRange GeckoTextMarker::Range(EWhichRange aRangeType) {
|
2021-07-22 20:58:49 +03:00
|
|
|
MOZ_ASSERT(mContainer);
|
|
|
|
if (mContainer->IsRemote()) {
|
2020-08-14 22:33:02 +03:00
|
|
|
int32_t startOffset = 0, endOffset = 0;
|
|
|
|
uint64_t startContainerID = 0, endContainerID = 0;
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
|
2020-09-24 19:04:41 +03:00
|
|
|
bool success = ipcDoc->GetPlatformExtension()->SendRangeAt(
|
2021-07-22 20:58:49 +03:00
|
|
|
mContainer->AsRemote()->ID(), mOffset, aRangeType, &startContainerID,
|
2020-09-24 19:04:41 +03:00
|
|
|
&startOffset, &endContainerID, &endOffset);
|
|
|
|
if (success) {
|
|
|
|
return GeckoTextMarkerRange(
|
|
|
|
GeckoTextMarker(ipcDoc->GetAccessible(startContainerID), startOffset),
|
|
|
|
GeckoTextMarker(ipcDoc->GetAccessible(endContainerID), endOffset));
|
|
|
|
}
|
2020-08-14 22:33:02 +03:00
|
|
|
} else if (auto htWrap = ContainerAsHyperTextWrap()) {
|
|
|
|
int32_t startOffset = 0, endOffset = 0;
|
|
|
|
HyperTextAccessible* startContainer = nullptr;
|
|
|
|
HyperTextAccessible* endContainer = nullptr;
|
2020-09-24 19:04:41 +03:00
|
|
|
htWrap->RangeAt(mOffset, aRangeType, &startContainer, &startOffset,
|
|
|
|
&endContainer, &endOffset);
|
2020-08-14 22:33:02 +03:00
|
|
|
return GeckoTextMarkerRange(GeckoTextMarker(startContainer, startOffset),
|
|
|
|
GeckoTextMarker(endContainer, endOffset));
|
|
|
|
}
|
|
|
|
|
|
|
|
return GeckoTextMarkerRange(GeckoTextMarker(), GeckoTextMarker());
|
2020-07-21 01:53:04 +03:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* GeckoTextMarker::Leaf() {
|
|
|
|
MOZ_ASSERT(mContainer);
|
|
|
|
if (mContainer->IsRemote()) {
|
2020-09-11 08:08:00 +03:00
|
|
|
uint64_t leafID = 0;
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
|
2020-09-11 08:08:00 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendLeafAtOffset(
|
2021-07-22 20:58:49 +03:00
|
|
|
mContainer->AsRemote()->ID(), mOffset, &leafID);
|
2020-09-11 08:08:00 +03:00
|
|
|
return ipcDoc->GetAccessible(leafID);
|
|
|
|
} else if (auto htWrap = ContainerAsHyperTextWrap()) {
|
|
|
|
return htWrap->LeafAtOffset(mOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
return mContainer;
|
|
|
|
}
|
|
|
|
|
2020-07-03 00:35:05 +03:00
|
|
|
// GeckoTextMarkerRange
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
GeckoTextMarkerRange::GeckoTextMarkerRange(
|
2021-07-22 20:58:49 +03:00
|
|
|
Accessible* aDoc, AXTextMarkerRangeRef aTextMarkerRange) {
|
2020-11-21 07:13:45 +03:00
|
|
|
if (!aTextMarkerRange ||
|
|
|
|
CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
|
2020-07-03 00:35:05 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRef start_marker(
|
|
|
|
AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
|
2020-07-03 00:35:05 +03:00
|
|
|
AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));
|
|
|
|
|
|
|
|
mStart = GeckoTextMarker(aDoc, start_marker);
|
|
|
|
mEnd = GeckoTextMarker(aDoc, end_marker);
|
|
|
|
|
|
|
|
CFRelease(start_marker);
|
|
|
|
CFRelease(end_marker);
|
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
GeckoTextMarkerRange::GeckoTextMarkerRange(Accessible* aAccessible) {
|
|
|
|
if (aAccessible->IsHyperText()) {
|
2020-12-02 02:24:17 +03:00
|
|
|
// The accessible is a hypertext. Initialize range to its inner text range.
|
|
|
|
mStart = GeckoTextMarker(aAccessible, 0);
|
|
|
|
mEnd = GeckoTextMarker(aAccessible, (CharacterCount(aAccessible)));
|
|
|
|
} else {
|
|
|
|
// The accessible is not a hypertext (maybe a text leaf?). Initialize range
|
|
|
|
// to its offsets in its container.
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart = GeckoTextMarker(aAccessible->Parent(), 0);
|
|
|
|
mEnd = GeckoTextMarker(aAccessible->Parent(), 0);
|
|
|
|
if (mStart.mContainer->IsRemote()) {
|
|
|
|
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
|
2020-12-02 02:24:17 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendRangeOfChild(
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart.mContainer->AsRemote()->ID(), aAccessible->AsRemote()->ID(),
|
2020-12-02 02:24:17 +03:00
|
|
|
&mStart.mOffset, &mEnd.mOffset);
|
|
|
|
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
|
2021-07-22 20:58:49 +03:00
|
|
|
htWrap->RangeOfChild(aAccessible->AsLocal(), &mStart.mOffset,
|
2020-12-02 02:24:17 +03:00
|
|
|
&mEnd.mOffset);
|
|
|
|
}
|
2020-09-11 08:07:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 00:35:05 +03:00
|
|
|
id GeckoTextMarkerRange::CreateAXTextMarkerRange() {
|
2020-10-30 07:41:20 +03:00
|
|
|
if (!IsValid()) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2020-08-26 00:40:32 +03:00
|
|
|
AXTextMarkerRangeRef cf_text_marker_range =
|
|
|
|
AXTextMarkerRangeCreate(kCFAllocatorDefault, mStart.CreateAXTextMarker(),
|
|
|
|
mEnd.CreateAXTextMarker());
|
2020-07-03 00:35:05 +03:00
|
|
|
return [static_cast<id>(cf_text_marker_range) autorelease];
|
|
|
|
}
|
|
|
|
|
2020-07-03 00:57:55 +03:00
|
|
|
NSString* GeckoTextMarkerRange::Text() const {
|
|
|
|
nsAutoString text;
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
|
|
|
|
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
|
2020-08-14 22:33:02 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendTextForRange(
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
|
|
|
|
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &text);
|
2020-08-14 22:33:02 +03:00
|
|
|
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
|
2020-08-26 00:40:32 +03:00
|
|
|
htWrap->TextForRange(text, mStart.mOffset, mEnd.ContainerAsHyperTextWrap(),
|
|
|
|
mEnd.mOffset);
|
2020-07-03 00:57:55 +03:00
|
|
|
}
|
2020-08-14 22:33:02 +03:00
|
|
|
return nsCocoaUtils::ToNSString(text);
|
2020-07-03 00:57:55 +03:00
|
|
|
}
|
2020-08-21 01:12:23 +03:00
|
|
|
|
2021-06-11 02:07:06 +03:00
|
|
|
static NSColor* ColorFromColor(const Color& aColor) {
|
|
|
|
return [NSColor colorWithCalibratedRed:NS_GET_R(aColor.mValue) / 255.0
|
|
|
|
green:NS_GET_G(aColor.mValue) / 255.0
|
|
|
|
blue:NS_GET_B(aColor.mValue) / 255.0
|
|
|
|
alpha:1.0];
|
2021-02-09 02:26:31 +03:00
|
|
|
}
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
static NSDictionary* StringAttributesFromAttributes(AccAttributes* aAttributes,
|
|
|
|
Accessible* aContainer) {
|
2021-02-09 02:26:31 +03:00
|
|
|
NSMutableDictionary* attrDict =
|
2021-06-11 02:07:05 +03:00
|
|
|
[NSMutableDictionary dictionaryWithCapacity:aAttributes->Count()];
|
2021-02-09 02:26:31 +03:00
|
|
|
NSMutableDictionary* fontAttrDict = [[NSMutableDictionary alloc] init];
|
|
|
|
[attrDict setObject:fontAttrDict forKey:@"AXFont"];
|
2021-06-11 02:07:05 +03:00
|
|
|
for (auto iter : *aAttributes) {
|
|
|
|
if (iter.Name() == nsGkAtoms::backgroundColor) {
|
2021-06-11 02:07:06 +03:00
|
|
|
if (Maybe<Color> value = iter.Value<Color>()) {
|
|
|
|
NSColor* color = ColorFromColor(*value);
|
2021-02-09 02:26:31 +03:00
|
|
|
[attrDict setObject:(__bridge id)color.CGColor
|
|
|
|
forKey:@"AXBackgroundColor"];
|
|
|
|
}
|
2021-06-11 02:07:05 +03:00
|
|
|
} else if (iter.Name() == nsGkAtoms::color) {
|
2021-06-11 02:07:06 +03:00
|
|
|
if (Maybe<Color> value = iter.Value<Color>()) {
|
|
|
|
NSColor* color = ColorFromColor(*value);
|
2021-02-09 02:26:31 +03:00
|
|
|
[attrDict setObject:(__bridge id)color.CGColor
|
|
|
|
forKey:@"AXForegroundColor"];
|
|
|
|
}
|
2021-06-11 02:07:05 +03:00
|
|
|
} else if (iter.Name() == nsGkAtoms::font_size) {
|
2021-06-11 02:07:06 +03:00
|
|
|
if (Maybe<FontSize> pointSize = iter.Value<FontSize>()) {
|
|
|
|
int32_t fontPixelSize = static_cast<int32_t>(pointSize->mValue * 4 / 3);
|
2021-02-09 02:26:31 +03:00
|
|
|
[fontAttrDict setObject:@(fontPixelSize) forKey:@"AXFontSize"];
|
|
|
|
}
|
2021-06-11 02:07:05 +03:00
|
|
|
} else if (iter.Name() == nsGkAtoms::font_family) {
|
2021-06-11 02:07:06 +03:00
|
|
|
nsAutoString fontFamily;
|
|
|
|
iter.ValueAsString(fontFamily);
|
|
|
|
[fontAttrDict setObject:nsCocoaUtils::ToNSString(fontFamily)
|
2021-06-11 02:07:05 +03:00
|
|
|
forKey:@"AXFontFamily"];
|
|
|
|
} else if (iter.Name() == nsGkAtoms::textUnderlineColor) {
|
2021-02-09 02:26:31 +03:00
|
|
|
[attrDict setObject:@1 forKey:@"AXUnderline"];
|
2021-06-11 02:07:06 +03:00
|
|
|
if (Maybe<Color> value = iter.Value<Color>()) {
|
|
|
|
NSColor* color = ColorFromColor(*value);
|
2021-02-09 02:26:31 +03:00
|
|
|
[attrDict setObject:(__bridge id)color.CGColor
|
|
|
|
forKey:@"AXUnderlineColor"];
|
|
|
|
}
|
2021-06-11 02:07:05 +03:00
|
|
|
} else if (iter.Name() == nsGkAtoms::invalid) {
|
2021-02-09 02:26:31 +03:00
|
|
|
// XXX: There is currently no attribute for grammar
|
2021-09-23 23:01:11 +03:00
|
|
|
if (auto value = iter.Value<RefPtr<nsAtom>>()) {
|
2021-06-11 02:07:06 +03:00
|
|
|
if (*value == nsGkAtoms::spelling) {
|
|
|
|
[attrDict setObject:@YES
|
|
|
|
forKey:NSAccessibilityMarkedMisspelledTextAttribute];
|
|
|
|
}
|
2021-02-09 02:26:31 +03:00
|
|
|
}
|
|
|
|
} else {
|
2021-06-11 02:07:05 +03:00
|
|
|
nsAutoString valueStr;
|
|
|
|
iter.ValueAsString(valueStr);
|
|
|
|
nsAutoString keyStr;
|
|
|
|
iter.NameAsString(keyStr);
|
|
|
|
[attrDict setObject:nsCocoaUtils::ToNSString(valueStr)
|
|
|
|
forKey:nsCocoaUtils::ToNSString(keyStr)];
|
2021-02-09 02:26:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mozAccessible* container = GetNativeFromGeckoAccessible(aContainer);
|
|
|
|
id<MOXAccessible> link =
|
|
|
|
[container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
|
|
|
|
return [[moxAcc moxRole] isEqualToString:NSAccessibilityLinkRole];
|
|
|
|
}];
|
|
|
|
if (link) {
|
|
|
|
[attrDict setObject:link forKey:@"AXLink"];
|
|
|
|
}
|
|
|
|
|
|
|
|
id<MOXAccessible> heading =
|
|
|
|
[container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
|
|
|
|
return [[moxAcc moxRole] isEqualToString:@"AXHeading"];
|
|
|
|
}];
|
|
|
|
if (heading) {
|
|
|
|
[attrDict setObject:[heading moxValue] forKey:@"AXHeadingLevel"];
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrDict;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
|
|
|
|
NSMutableAttributedString* str =
|
|
|
|
[[[NSMutableAttributedString alloc] init] autorelease];
|
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
|
2021-02-09 02:26:47 +03:00
|
|
|
nsTArray<TextAttributesRun> textAttributesRuns;
|
2021-07-22 20:58:49 +03:00
|
|
|
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
|
2021-02-09 02:26:47 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendAttributedTextForRange(
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
|
|
|
|
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &textAttributesRuns);
|
2021-02-09 02:26:47 +03:00
|
|
|
|
|
|
|
for (size_t i = 0; i < textAttributesRuns.Length(); i++) {
|
2021-06-11 02:07:07 +03:00
|
|
|
AccAttributes* attributes =
|
2021-02-09 02:26:47 +03:00
|
|
|
textAttributesRuns.ElementAt(i).TextAttributes();
|
2021-02-20 02:14:33 +03:00
|
|
|
RemoteAccessible* container =
|
2021-02-09 02:26:47 +03:00
|
|
|
ipcDoc->GetAccessible(textAttributesRuns.ElementAt(i).ContainerID());
|
|
|
|
|
|
|
|
NSAttributedString* substr = [[[NSAttributedString alloc]
|
|
|
|
initWithString:nsCocoaUtils::ToNSString(
|
|
|
|
textAttributesRuns.ElementAt(i).Text())
|
|
|
|
attributes:StringAttributesFromAttributes(attributes, container)]
|
|
|
|
autorelease];
|
|
|
|
|
|
|
|
[str appendAttributedString:substr];
|
|
|
|
}
|
2021-02-09 02:26:31 +03:00
|
|
|
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
|
|
|
|
nsTArray<nsString> texts;
|
2021-02-20 02:14:32 +03:00
|
|
|
nsTArray<LocalAccessible*> containers;
|
2021-06-11 02:07:05 +03:00
|
|
|
nsTArray<RefPtr<AccAttributes>> props;
|
2021-02-09 02:26:31 +03:00
|
|
|
|
|
|
|
htWrap->AttributedTextForRange(texts, props, containers, mStart.mOffset,
|
|
|
|
mEnd.ContainerAsHyperTextWrap(),
|
|
|
|
mEnd.mOffset);
|
|
|
|
|
|
|
|
MOZ_ASSERT(texts.Length() == props.Length() &&
|
|
|
|
texts.Length() == containers.Length());
|
|
|
|
|
|
|
|
for (size_t i = 0; i < texts.Length(); i++) {
|
|
|
|
NSAttributedString* substr = [[[NSAttributedString alloc]
|
|
|
|
initWithString:nsCocoaUtils::ToNSString(texts.ElementAt(i))
|
|
|
|
attributes:StringAttributesFromAttributes(
|
2021-06-11 02:07:05 +03:00
|
|
|
props.ElementAt(i), containers.ElementAt(i))]
|
|
|
|
autorelease];
|
2021-02-09 02:26:31 +03:00
|
|
|
[str appendAttributedString:substr];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:06:51 +03:00
|
|
|
int32_t GeckoTextMarkerRange::Length() const {
|
|
|
|
int32_t length = 0;
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
|
|
|
|
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
|
2020-08-27 23:06:51 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendLengthForRange(
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
|
|
|
|
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &length);
|
2020-08-27 23:06:51 +03:00
|
|
|
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
|
|
|
|
length = htWrap->LengthForRange(
|
|
|
|
mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2020-08-21 01:12:23 +03:00
|
|
|
NSValue* GeckoTextMarkerRange::Bounds() const {
|
|
|
|
nsIntRect rect;
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
|
|
|
|
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
|
2020-08-21 01:12:23 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendBoundsForRange(
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
|
|
|
|
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &rect);
|
2020-08-21 01:12:23 +03:00
|
|
|
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
|
2020-08-26 00:40:32 +03:00
|
|
|
rect = htWrap->BoundsForRange(
|
|
|
|
mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
|
2020-08-21 01:12:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
|
|
|
|
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
|
2020-08-26 00:40:32 +03:00
|
|
|
NSRect r =
|
|
|
|
NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor,
|
|
|
|
[mainView frame].size.height -
|
|
|
|
static_cast<CGFloat>(rect.y + rect.height) / scaleFactor,
|
|
|
|
static_cast<CGFloat>(rect.width) / scaleFactor,
|
|
|
|
static_cast<CGFloat>(rect.height) / scaleFactor);
|
2020-08-21 01:12:23 +03:00
|
|
|
|
|
|
|
return [NSValue valueWithRect:r];
|
|
|
|
}
|
2020-09-22 01:00:08 +03:00
|
|
|
|
|
|
|
void GeckoTextMarkerRange::Select() const {
|
2021-07-22 20:58:49 +03:00
|
|
|
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
|
|
|
|
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
|
2020-09-22 01:00:08 +03:00
|
|
|
Unused << ipcDoc->GetPlatformExtension()->SendSelectRange(
|
2021-07-22 20:58:49 +03:00
|
|
|
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
|
|
|
|
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset);
|
2020-09-22 01:00:08 +03:00
|
|
|
} else if (RefPtr<HyperTextAccessibleWrap> htWrap =
|
|
|
|
mStart.ContainerAsHyperTextWrap()) {
|
|
|
|
RefPtr<HyperTextAccessibleWrap> end = mEnd.ContainerAsHyperTextWrap();
|
|
|
|
htWrap->SelectRange(mStart.mOffset, end, mEnd.mOffset);
|
|
|
|
}
|
|
|
|
}
|
2020-11-21 07:13:45 +03:00
|
|
|
|
2021-07-22 20:58:49 +03:00
|
|
|
bool GeckoTextMarkerRange::Crop(Accessible* aContainer) {
|
2020-11-21 07:13:45 +03:00
|
|
|
GeckoTextMarker containerStart(aContainer, 0);
|
|
|
|
GeckoTextMarker containerEnd(aContainer, CharacterCount(aContainer));
|
|
|
|
|
|
|
|
if (mEnd < containerStart || containerEnd < mStart) {
|
|
|
|
// The range ends before the container, or starts after it.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mStart < containerStart) {
|
|
|
|
// If range start is before container start, adjust range start to
|
|
|
|
// start of container.
|
|
|
|
mStart = containerStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (containerEnd < mEnd) {
|
|
|
|
// If range end is after container end, adjust range end to end of
|
|
|
|
// container.
|
|
|
|
mEnd = containerEnd;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2020-07-03 00:35:05 +03:00
|
|
|
}
|
|
|
|
}
|