Bug 1821965 - P1: Remove legacy and proxy text marker classes. r=morgan

With caching on we can remove the legacy text marker and the abstracting class that allowed us to operate in both modes.

Differential Revision: https://phabricator.services.mozilla.com/D178717
This commit is contained in:
Eitan Isaacson 2023-05-25 20:38:29 +00:00
Родитель 2237be2626
Коммит de34976841
7 изменённых файлов: 425 добавлений и 1309 удалений

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

@ -1,129 +0,0 @@
/* clang-format off */
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* clang-format on */
/* 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 _CachedTextMarker_H_
#define _CachedTextMarker_H_
#include <ApplicationServices/ApplicationServices.h>
#include <Foundation/Foundation.h>
#include "TextLeafRange.h"
namespace mozilla {
namespace a11y {
class Accessible;
class CachedTextMarkerRange;
class CachedTextMarker final {
public:
CachedTextMarker(Accessible* aAcc, int32_t aOffset);
explicit CachedTextMarker(const TextLeafPoint& aTextLeafPoint)
: mPoint(aTextLeafPoint) {}
CachedTextMarker() : mPoint() {}
CachedTextMarker(Accessible* aDoc, AXTextMarkerRef aTextMarker);
static CachedTextMarker MarkerFromIndex(Accessible* aRoot, int32_t aIndex);
AXTextMarkerRef CreateAXTextMarker();
bool Next();
bool Previous();
CachedTextMarkerRange LeftWordRange() const;
CachedTextMarkerRange RightWordRange() const;
CachedTextMarkerRange LineRange() const;
CachedTextMarkerRange LeftLineRange() const;
CachedTextMarkerRange RightLineRange() const;
CachedTextMarkerRange ParagraphRange() const;
CachedTextMarkerRange StyleRange() const;
Accessible* Leaf();
bool IsValid() const { return !!mPoint; };
bool operator<(const CachedTextMarker& aOther) const {
return mPoint < aOther.mPoint;
}
bool operator==(const CachedTextMarker& aOther) const {
return mPoint == aOther.mPoint;
}
TextLeafPoint mPoint;
};
class CachedTextMarkerRange final {
public:
CachedTextMarkerRange(const CachedTextMarker& aStart,
const CachedTextMarker& aEnd)
: mRange(aStart.mPoint, aEnd.mPoint) {}
CachedTextMarkerRange(const TextLeafPoint& aStart, const TextLeafPoint& aEnd)
: mRange(aStart, aEnd) {}
CachedTextMarkerRange() {}
CachedTextMarkerRange(Accessible* aDoc,
AXTextMarkerRangeRef aTextMarkerRange);
explicit CachedTextMarkerRange(Accessible* aAccessible);
AXTextMarkerRangeRef CreateAXTextMarkerRange();
bool IsValid() const { return !!mRange.Start() && !!mRange.End(); };
/**
* Return text enclosed by the range.
*/
NSString* Text() const;
/**
* Return the attributed text enclosed by the range.
*/
NSAttributedString* AttributedText() const;
/**
* Return length of characters enclosed by the range.
*/
int32_t Length() const;
/**
* Return screen bounds of range.
*/
NSValue* Bounds() const;
/**
* Set the current range as the DOM selection.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY void Select() const;
/**
* Crops the range if it overlaps the given accessible element boundaries.
* Return true if successfully cropped. false if the range does not intersect
* with the container.
*/
bool Crop(Accessible* aContainer);
TextLeafRange mRange;
};
} // namespace a11y
} // namespace mozilla
#endif

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

@ -1,435 +0,0 @@
/* clang-format off */
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* clang-format on */
/* 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/. */
#import "CachedTextMarker.h"
#import "MacUtils.h"
#include "AccAttributes.h"
#include "nsCocoaUtils.h"
#include "HyperTextAccessible.h"
#include "States.h"
#include "nsAccUtils.h"
namespace mozilla {
namespace a11y {
// CachedTextMarker
CachedTextMarker::CachedTextMarker(Accessible* aAcc, int32_t aOffset) {
HyperTextAccessibleBase* ht = aAcc->AsHyperTextBase();
if (ht && aOffset != nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
aOffset <= static_cast<int32_t>(ht->CharacterCount())) {
mPoint = aAcc->AsHyperTextBase()->ToTextLeafPoint(aOffset);
} else {
mPoint = TextLeafPoint(aAcc, aOffset);
}
}
CachedTextMarker CachedTextMarker::MarkerFromIndex(Accessible* aRoot,
int32_t aIndex) {
TextLeafRange range(
TextLeafPoint(aRoot, 0),
TextLeafPoint(aRoot, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
int32_t index = aIndex;
// Iterate through all segments until we exhausted the index sum
// so we can find the segment the index lives in.
for (TextLeafRange segment : range) {
if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
// XXX: MacOS expects bullets to be in the range's text, but not in
// the calculated length!
continue;
}
index -= segment.End().mOffset - segment.Start().mOffset;
if (index <= 0) {
// The index is in the current segment.
return CachedTextMarker(segment.Start().mAcc,
segment.End().mOffset + index);
}
}
return CachedTextMarker();
}
bool CachedTextMarker::Next() {
TextLeafPoint next =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
if (next && next != mPoint) {
mPoint = next;
return true;
}
return false;
}
bool CachedTextMarker::Previous() {
TextLeafPoint prev =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
if (prev && mPoint != prev) {
mPoint = prev;
return true;
}
return false;
}
/**
* Return true if the given point is inside editable content.
*/
static bool IsPointInEditable(const TextLeafPoint& aPoint) {
if (aPoint.mAcc) {
if (aPoint.mAcc->State() & states::EDITABLE) {
return true;
}
Accessible* parent = aPoint.mAcc->Parent();
if (parent && (parent->State() & states::EDITABLE)) {
return true;
}
}
return false;
}
CachedTextMarkerRange CachedTextMarker::LeftWordRange() const {
bool includeCurrentInStart = !mPoint.IsParagraphStart(true);
if (includeCurrentInStart) {
TextLeafPoint prevChar =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
if (!prevChar.IsSpace()) {
includeCurrentInStart = false;
}
}
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
includeCurrentInStart
? (TextLeafPoint::BoundaryFlags::eIncludeOrigin |
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker)
: (TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker));
TextLeafPoint end;
if (start == mPoint) {
end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
}
if (start != mPoint || end == start) {
end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (end < mPoint && IsPointInEditable(end) && !IsPointInEditable(mPoint)) {
start = end;
end = mPoint;
}
}
return CachedTextMarkerRange(start < end ? start : end,
start < end ? end : start);
}
CachedTextMarkerRange CachedTextMarker::RightWordRange() const {
TextLeafPoint prevChar =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (prevChar != mPoint && mPoint.IsParagraphStart(true)) {
return CachedTextMarkerRange(mPoint, mPoint);
}
TextLeafPoint end =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (end == mPoint) {
// No word to the right of this point.
return CachedTextMarkerRange(mPoint, mPoint);
}
TextLeafPoint start =
end.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable) <
mPoint) {
// Word end is inside of an input to the left of this.
return CachedTextMarkerRange(mPoint, mPoint);
}
if (mPoint < start) {
end = start;
start = mPoint;
}
return CachedTextMarkerRange(start < end ? start : end,
start < end ? end : start);
}
CachedTextMarkerRange CachedTextMarker::LineRange() const {
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker |
TextLeafPoint::BoundaryFlags::eIncludeOrigin);
// If this is a blank line containing only a line feed, the start boundary
// is the same as the end boundary. We do not want to walk to the end of the
// next line.
TextLeafPoint end =
start.IsLineFeedChar()
? start
: start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return CachedTextMarkerRange(start, end);
}
CachedTextMarkerRange CachedTextMarker::LeftLineRange() const {
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
TextLeafPoint end =
start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return CachedTextMarkerRange(start, end);
}
CachedTextMarkerRange CachedTextMarker::RightLineRange() const {
TextLeafPoint end =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
TextLeafPoint start =
end.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return CachedTextMarkerRange(start, end);
}
CachedTextMarkerRange CachedTextMarker::ParagraphRange() const {
// XXX: WebKit gets trapped in inputs. Maybe we shouldn't?
TextLeafPoint end =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
TextLeafPoint start =
end.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return CachedTextMarkerRange(start, end);
}
CachedTextMarkerRange CachedTextMarker::StyleRange() const {
if (mPoint.mOffset == 0) {
// If the marker is on the boundary between two leafs, MacOS expects the
// previous leaf.
TextLeafPoint prev = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
if (prev != mPoint) {
return CachedTextMarker(prev).StyleRange();
}
}
TextLeafPoint start(mPoint.mAcc, 0);
TextLeafPoint end(mPoint.mAcc, nsAccUtils::TextLength(mPoint.mAcc));
return CachedTextMarkerRange(start, end);
}
Accessible* CachedTextMarker::Leaf() {
MOZ_ASSERT(mPoint.mAcc);
Accessible* acc = mPoint.mAcc;
if (mPoint.mOffset == 0) {
// If the marker is on the boundary between two leafs, MacOS expects the
// previous leaf.
TextLeafPoint prev = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
acc = prev.mAcc;
}
Accessible* parent = acc->Parent();
return parent && nsAccUtils::MustPrune(parent) ? parent : acc;
}
// CachedTextMarkerRange
CachedTextMarkerRange::CachedTextMarkerRange(Accessible* aAccessible) {
mRange = TextLeafRange(
TextLeafPoint(aAccessible, 0),
TextLeafPoint(aAccessible, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
}
NSString* CachedTextMarkerRange::Text() const {
if (mRange.Start() == mRange.End()) {
return @"";
}
if ((mRange.Start().mAcc == mRange.End().mAcc) &&
(mRange.Start().mAcc->ChildCount() == 0) &&
(mRange.Start().mAcc->State() & states::EDITABLE)) {
return @"";
}
nsAutoString text;
TextLeafPoint prev = mRange.Start().FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
TextLeafRange range =
prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
: mRange;
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
continue;
}
start.mAcc->AppendTextTo(text, start.mOffset,
segment.End().mOffset - start.mOffset);
}
return nsCocoaUtils::ToNSString(text);
}
static void AppendTextToAttributedString(
NSMutableAttributedString* aAttributedString, Accessible* aAccessible,
const nsString& aString, AccAttributes* aAttributes) {
NSAttributedString* substr = [[[NSAttributedString alloc]
initWithString:nsCocoaUtils::ToNSString(aString)
attributes:utils::StringAttributesFromAccAttributes(
aAttributes, aAccessible)] autorelease];
[aAttributedString appendAttributedString:substr];
}
NSAttributedString* CachedTextMarkerRange::AttributedText() const {
NSMutableAttributedString* str =
[[[NSMutableAttributedString alloc] init] autorelease];
if (mRange.Start() == mRange.End()) {
return str;
}
if ((mRange.Start().mAcc == mRange.End().mAcc) &&
(mRange.Start().mAcc->ChildCount() == 0) &&
(mRange.Start().mAcc->IsTextField())) {
return str;
}
TextLeafPoint prev = mRange.Start().FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
TextLeafRange range =
prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
: mRange;
nsAutoString text;
RefPtr<AccAttributes> currentRun = nullptr;
Accessible* runAcc = range.Start().mAcc;
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
continue;
}
if (!currentRun) {
// This is the first segment that isn't an empty input.
currentRun = start.GetTextAttributes();
}
TextLeafPoint attributesNext;
do {
attributesNext = start.FindTextAttrsStart(eDirNext, false);
if (attributesNext == start) {
// XXX: FindTextAttrsStart should not return the same point.
break;
}
RefPtr<AccAttributes> attributes = start.GetTextAttributes();
MOZ_ASSERT(attributes);
if (attributes && !attributes->Equal(currentRun)) {
AppendTextToAttributedString(str, runAcc, text, currentRun);
text.Truncate();
currentRun = attributes;
runAcc = start.mAcc;
}
TextLeafPoint end =
attributesNext < segment.End() ? attributesNext : segment.End();
start.mAcc->AppendTextTo(text, start.mOffset,
end.mOffset - start.mOffset);
start = attributesNext;
} while (attributesNext < segment.End());
}
if (!text.IsEmpty()) {
AppendTextToAttributedString(str, runAcc, text, currentRun);
}
return str;
}
int32_t CachedTextMarkerRange::Length() const {
int32_t length = 0;
for (TextLeafRange segment : mRange) {
if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
// XXX: MacOS expects bullets to be in the range's text, but not in
// the calculated length!
continue;
}
length += segment.End().mOffset - segment.Start().mOffset;
}
return length;
}
NSValue* CachedTextMarkerRange::Bounds() const {
LayoutDeviceIntRect rect = mRange ? mRange.Bounds() : LayoutDeviceIntRect();
NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
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);
return [NSValue valueWithRect:r];
}
void CachedTextMarkerRange::Select() const { mRange.SetSelection(0); }
bool CachedTextMarkerRange::Crop(Accessible* aContainer) {
TextLeafPoint containerStart(aContainer, 0);
TextLeafPoint containerEnd(aContainer,
nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
if (mRange.End() < containerStart || containerEnd < mRange.Start()) {
// The range ends before the container, or starts after it.
return false;
}
if (mRange.Start() < containerStart) {
// If range start is before container start, adjust range start to
// start of container.
mRange.SetStart(containerStart);
}
if (containerEnd < mRange.End()) {
// If range end is after container end, adjust range end to end of
// container.
mRange.SetEnd(containerEnd);
}
return true;
}
} // namespace a11y
} // namespace mozilla

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

@ -9,9 +9,10 @@
#ifndef _GeckoTextMarker_H_
#define _GeckoTextMarker_H_
#include <ApplicationServices/ApplicationServices.h>
#include <Foundation/Foundation.h>
#import "LegacyTextMarker.h"
#import "CachedTextMarker.h"
#include "TextLeafRange.h"
namespace mozilla {
namespace a11y {
@ -19,29 +20,14 @@ namespace a11y {
class Accessible;
class GeckoTextMarkerRange;
class GeckoTextMarker {
class GeckoTextMarker final {
public:
GeckoTextMarker();
GeckoTextMarker(const GeckoTextMarker& aOther) {
mLegacy = aOther.mLegacy;
if (mLegacy) {
mLegacyTextMarker = aOther.mLegacyTextMarker;
} else {
mCachedTextMarker = aOther.mCachedTextMarker;
}
}
explicit GeckoTextMarker(const LegacyTextMarker& aTextMarker)
: mLegacy(true), mLegacyTextMarker(aTextMarker) {}
explicit GeckoTextMarker(const CachedTextMarker& aTextMarker)
: mLegacy(false), mCachedTextMarker(aTextMarker) {}
GeckoTextMarker(Accessible* aAcc, int32_t aOffset);
explicit GeckoTextMarker(const TextLeafPoint& aTextLeafPoint)
: mLegacy(false), mCachedTextMarker(aTextLeafPoint) {}
: mPoint(aTextLeafPoint) {}
GeckoTextMarker(Accessible* aContainer, int32_t aOffset);
GeckoTextMarker() : mPoint() {}
static GeckoTextMarker MarkerFromAXTextMarker(Accessible* aDoc,
AXTextMarkerRef aTextMarker);
@ -50,14 +36,9 @@ class GeckoTextMarker {
AXTextMarkerRef CreateAXTextMarker();
bool Next() {
return mLegacy ? mLegacyTextMarker.Next() : mCachedTextMarker.Next();
}
bool Next();
bool Previous() {
return mLegacy ? mLegacyTextMarker.Previous()
: mCachedTextMarker.Previous();
}
bool Previous();
GeckoTextMarkerRange LeftWordRange() const;
@ -73,75 +54,35 @@ class GeckoTextMarker {
GeckoTextMarkerRange StyleRange() const;
Accessible* Leaf() {
return mLegacy ? mLegacyTextMarker.Leaf() : mCachedTextMarker.Leaf();
int32_t& Offset() { return mPoint.mOffset; }
Accessible* Leaf();
Accessible* Acc() const { return mPoint.mAcc; }
bool IsValid() const { return !!mPoint; };
bool operator<(const GeckoTextMarker& aOther) const {
return mPoint < aOther.mPoint;
}
int32_t& Offset() {
return mLegacy ? mLegacyTextMarker.mOffset
: mCachedTextMarker.mPoint.mOffset;
bool operator==(const GeckoTextMarker& aOther) const {
return mPoint == aOther.mPoint;
}
Accessible* Acc() const {
return mLegacy ? mLegacyTextMarker.mContainer
: mCachedTextMarker.mPoint.mAcc;
}
bool IsValid() const {
return mLegacy ? mLegacyTextMarker.IsValid() : mCachedTextMarker.IsValid();
}
bool operator<(const GeckoTextMarker& aPoint) const {
return mLegacy ? (mLegacyTextMarker < aPoint.mLegacyTextMarker)
: (mCachedTextMarker < aPoint.mCachedTextMarker);
}
bool operator==(const GeckoTextMarker& aPoint) const {
return mLegacy ? (mLegacyTextMarker == aPoint.mLegacyTextMarker)
: (mCachedTextMarker == aPoint.mCachedTextMarker);
}
private:
bool mLegacy;
union {
LegacyTextMarker mLegacyTextMarker;
CachedTextMarker mCachedTextMarker;
};
friend class GeckoTextMarkerRange;
TextLeafPoint mPoint;
};
class GeckoTextMarkerRange {
class GeckoTextMarkerRange final {
public:
GeckoTextMarkerRange();
GeckoTextMarkerRange(const GeckoTextMarkerRange& aOther) {
mLegacy = aOther.mLegacy;
if (mLegacy) {
mLegacyTextMarkerRange = aOther.mLegacyTextMarkerRange;
} else {
mCachedTextMarkerRange = aOther.mCachedTextMarkerRange;
}
}
explicit GeckoTextMarkerRange(const LegacyTextMarkerRange& aTextMarkerRange)
: mLegacy(true), mLegacyTextMarkerRange(aTextMarkerRange) {}
explicit GeckoTextMarkerRange(const CachedTextMarkerRange& aTextMarkerRange)
: mLegacy(false), mCachedTextMarkerRange(aTextMarkerRange) {}
GeckoTextMarkerRange(const GeckoTextMarker& aStart,
const GeckoTextMarker& aEnd) {
MOZ_ASSERT(aStart.mLegacy == aEnd.mLegacy);
mLegacy = aStart.mLegacy;
if (mLegacy) {
mLegacyTextMarkerRange = LegacyTextMarkerRange(aStart.mLegacyTextMarker,
aEnd.mLegacyTextMarker);
} else {
mCachedTextMarkerRange = CachedTextMarkerRange(aStart.mCachedTextMarker,
aEnd.mCachedTextMarker);
}
}
const GeckoTextMarker& aEnd)
: mRange(aStart.mPoint, aEnd.mPoint) {}
GeckoTextMarkerRange(const TextLeafPoint& aStart, const TextLeafPoint& aEnd)
: mRange(aStart, aEnd) {}
GeckoTextMarkerRange() {}
explicit GeckoTextMarkerRange(Accessible* aAccessible);
@ -150,76 +91,45 @@ class GeckoTextMarkerRange {
AXTextMarkerRangeRef CreateAXTextMarkerRange();
bool IsValid() const {
return mLegacy ? mLegacyTextMarkerRange.IsValid()
: mCachedTextMarkerRange.IsValid();
}
bool IsValid() const { return !!mRange.Start() && !!mRange.End(); };
GeckoTextMarker Start() {
return mLegacy ? GeckoTextMarker(mLegacyTextMarkerRange.mStart)
: GeckoTextMarker(mCachedTextMarkerRange.mRange.Start());
}
GeckoTextMarker Start() { return GeckoTextMarker(mRange.Start()); }
GeckoTextMarker End() {
return mLegacy ? GeckoTextMarker(mLegacyTextMarkerRange.mEnd)
: GeckoTextMarker(mCachedTextMarkerRange.mRange.End());
}
GeckoTextMarker End() { return GeckoTextMarker(mRange.End()); }
/**
* Return text enclosed by the range.
*/
NSString* Text() const {
return mLegacy ? mLegacyTextMarkerRange.Text()
: mCachedTextMarkerRange.Text();
}
NSString* Text() const;
/**
* Return the attributed text enclosed by the range.
*/
NSAttributedString* AttributedText() const {
return mLegacy ? mLegacyTextMarkerRange.AttributedText()
: mCachedTextMarkerRange.AttributedText();
}
NSAttributedString* AttributedText() const;
/**
* Return length of characters enclosed by the range.
*/
int32_t Length() const {
return mLegacy ? mLegacyTextMarkerRange.Length()
: mCachedTextMarkerRange.Length();
}
int32_t Length() const;
/**
* Return screen bounds of range.
*/
NSValue* Bounds() const {
return mLegacy ? mLegacyTextMarkerRange.Bounds()
: mCachedTextMarkerRange.Bounds();
}
NSValue* Bounds() const;
/**
* Set the current range as the DOM selection.
*/
void Select() const {
mLegacy ? mLegacyTextMarkerRange.Select() : mCachedTextMarkerRange.Select();
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY void Select() const;
/**
* Crops the range if it overlaps the given accessible element boundaries.
* Return true if successfully cropped. false if the range does not intersect
* with the container.
*/
bool Crop(Accessible* aContainer) {
return mLegacy ? mLegacyTextMarkerRange.Crop(aContainer)
: mCachedTextMarkerRange.Crop(aContainer);
}
bool Crop(Accessible* aContainer);
private:
bool mLegacy;
union {
LegacyTextMarkerRange mLegacyTextMarkerRange;
CachedTextMarkerRange mCachedTextMarkerRange;
};
TextLeafRange mRange;
};
} // namespace a11y

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

@ -9,51 +9,34 @@
#import "MacUtils.h"
#include "nsAccUtils.h"
#include "DocAccessible.h"
#include "AccAttributes.h"
#include "DocAccessibleParent.h"
#include "nsAccessibilityService.h"
#include "nsCocoaUtils.h"
#include "HyperTextAccessible.h"
#include "States.h"
#include "nsAccUtils.h"
namespace mozilla {
namespace a11y {
struct TextMarkerData {
TextMarkerData(uintptr_t aDoc, uintptr_t aID, int32_t aOffset, bool aLegacy)
: mDoc(aDoc), mID(aID), mOffset(aOffset), mLegacy(aLegacy) {}
TextMarkerData(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
: mDoc(aDoc), mID(aID), mOffset(aOffset) {}
TextMarkerData() {}
uintptr_t mDoc;
uintptr_t mID;
int32_t mOffset;
bool mLegacy;
};
// LegacyTextMarker
// GeckoTextMarker
GeckoTextMarker::GeckoTextMarker() {
if (a11y::IsCacheActive()) {
mLegacy = false;
mCachedTextMarker = CachedTextMarker();
GeckoTextMarker::GeckoTextMarker(Accessible* aAcc, int32_t aOffset) {
HyperTextAccessibleBase* ht = aAcc->AsHyperTextBase();
if (ht && aOffset != nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
aOffset <= static_cast<int32_t>(ht->CharacterCount())) {
mPoint = aAcc->AsHyperTextBase()->ToTextLeafPoint(aOffset);
} else {
mLegacy = true;
mLegacyTextMarker = LegacyTextMarker();
}
}
GeckoTextMarker::GeckoTextMarker(Accessible* aContainer, int32_t aOffset) {
if (a11y::IsCacheActive()) {
mLegacy = false;
mCachedTextMarker = CachedTextMarker(aContainer, aOffset);
} else {
mLegacy = true;
int32_t offset = aOffset;
if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) {
offset = aContainer->IsRemote() ? aContainer->AsRemote()->CharacterCount()
: aContainer->AsLocal()
->Document()
->AsHyperText()
->CharacterCount();
}
mLegacyTextMarker = LegacyTextMarker(aContainer, offset);
mPoint = TextLeafPoint(aAcc, aOffset);
}
}
@ -72,7 +55,6 @@ GeckoTextMarker GeckoTextMarker::MarkerFromAXTextMarker(
TextMarkerData markerData;
memcpy(&markerData, AXTextMarkerGetBytePtr(aTextMarker),
sizeof(TextMarkerData));
MOZ_ASSERT(a11y::IsCacheActive() == !markerData.mLegacy);
if (!utils::DocumentExists(aDoc, markerData.mDoc)) {
return GeckoTextMarker();
@ -98,11 +80,28 @@ GeckoTextMarker GeckoTextMarker::MarkerFromAXTextMarker(
GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
int32_t aIndex) {
if (a11y::IsCacheActive()) {
return GeckoTextMarker(CachedTextMarker::MarkerFromIndex(aRoot, aIndex));
TextLeafRange range(
TextLeafPoint(aRoot, 0),
TextLeafPoint(aRoot, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
int32_t index = aIndex;
// Iterate through all segments until we exhausted the index sum
// so we can find the segment the index lives in.
for (TextLeafRange segment : range) {
if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
// XXX: MacOS expects bullets to be in the range's text, but not in
// the calculated length!
continue;
}
index -= segment.End().mOffset - segment.Start().mOffset;
if (index <= 0) {
// The index is in the current segment.
return GeckoTextMarker(segment.Start().mAcc,
segment.End().mOffset + index);
}
}
return GeckoTextMarker(LegacyTextMarker::MarkerFromIndex(aRoot, aIndex));
return GeckoTextMarker();
}
AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
@ -110,13 +109,9 @@ AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
return nil;
}
Accessible* acc =
mLegacy ? mLegacyTextMarker.mContainer : mCachedTextMarker.mPoint.mAcc;
int32_t offset =
mLegacy ? mLegacyTextMarker.mOffset : mCachedTextMarker.mPoint.mOffset;
Accessible* doc = nsAccUtils::DocumentFor(acc);
TextMarkerData markerData(reinterpret_cast<uintptr_t>(doc), acc->ID(), offset,
mLegacy);
Accessible* doc = nsAccUtils::DocumentFor(mPoint.mAcc);
TextMarkerData markerData(reinterpret_cast<uintptr_t>(doc), mPoint.mAcc->ID(),
mPoint.mOffset);
AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&markerData),
sizeof(TextMarkerData));
@ -124,85 +119,217 @@ AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
return (__bridge AXTextMarkerRef)[(__bridge id)(cf_text_marker)autorelease];
}
GeckoTextMarkerRange GeckoTextMarker::LeftWordRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eLeftWord));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.LeftWordRange());
bool GeckoTextMarker::Next() {
TextLeafPoint next =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
if (next && next != mPoint) {
mPoint = next;
return true;
}
return false;
}
bool GeckoTextMarker::Previous() {
TextLeafPoint prev =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
if (prev && mPoint != prev) {
mPoint = prev;
return true;
}
return false;
}
/**
* Return true if the given point is inside editable content.
*/
static bool IsPointInEditable(const TextLeafPoint& aPoint) {
if (aPoint.mAcc) {
if (aPoint.mAcc->State() & states::EDITABLE) {
return true;
}
Accessible* parent = aPoint.mAcc->Parent();
if (parent && (parent->State() & states::EDITABLE)) {
return true;
}
}
return false;
}
GeckoTextMarkerRange GeckoTextMarker::LeftWordRange() const {
bool includeCurrentInStart = !mPoint.IsParagraphStart(true);
if (includeCurrentInStart) {
TextLeafPoint prevChar =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
if (!prevChar.IsSpace()) {
includeCurrentInStart = false;
}
}
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
includeCurrentInStart
? (TextLeafPoint::BoundaryFlags::eIncludeOrigin |
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker)
: (TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker));
TextLeafPoint end;
if (start == mPoint) {
end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
}
if (start != mPoint || end == start) {
end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (end < mPoint && IsPointInEditable(end) && !IsPointInEditable(mPoint)) {
start = end;
end = mPoint;
}
}
return GeckoTextMarkerRange(start < end ? start : end,
start < end ? end : start);
}
GeckoTextMarkerRange GeckoTextMarker::RightWordRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eRightWord));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.RightWordRange());
TextLeafPoint prevChar =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (prevChar != mPoint && mPoint.IsParagraphStart(true)) {
return GeckoTextMarkerRange(mPoint, mPoint);
}
TextLeafPoint end =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (end == mPoint) {
// No word to the right of this point.
return GeckoTextMarkerRange(mPoint, mPoint);
}
TextLeafPoint start =
end.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
if (start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable) <
mPoint) {
// Word end is inside of an input to the left of this.
return GeckoTextMarkerRange(mPoint, mPoint);
}
if (mPoint < start) {
end = start;
start = mPoint;
}
return GeckoTextMarkerRange(start < end ? start : end,
start < end ? end : start);
}
GeckoTextMarkerRange GeckoTextMarker::LineRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eLeftLine));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.LineRange());
}
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker |
TextLeafPoint::BoundaryFlags::eIncludeOrigin);
// If this is a blank line containing only a line feed, the start boundary
// is the same as the end boundary. We do not want to walk to the end of the
// next line.
TextLeafPoint end =
start.IsLineFeedChar()
? start
: start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return GeckoTextMarkerRange(start, end);
}
GeckoTextMarkerRange GeckoTextMarker::LeftLineRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eLeftLine));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.LeftLineRange());
}
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
TextLeafPoint end =
start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return GeckoTextMarkerRange(start, end);
}
GeckoTextMarkerRange GeckoTextMarker::RightLineRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eRightLine));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.RightLineRange());
}
TextLeafPoint end =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
TextLeafPoint start =
end.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return GeckoTextMarkerRange(start, end);
}
GeckoTextMarkerRange GeckoTextMarker::ParagraphRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eParagraph));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.ParagraphRange());
}
// XXX: WebKit gets trapped in inputs. Maybe we shouldn't?
TextLeafPoint end =
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
TextLeafPoint start =
end.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return GeckoTextMarkerRange(start, end);
}
GeckoTextMarkerRange GeckoTextMarker::StyleRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(mLegacyTextMarker.Range(EWhichRange::eStyle));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.StyleRange());
if (mPoint.mOffset == 0) {
// If the marker is on the boundary between two leafs, MacOS expects the
// previous leaf.
TextLeafPoint prev = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
if (prev != mPoint) {
return GeckoTextMarker(prev).StyleRange();
}
}
TextLeafPoint start(mPoint.mAcc, 0);
TextLeafPoint end(mPoint.mAcc, nsAccUtils::TextLength(mPoint.mAcc));
return GeckoTextMarkerRange(start, end);
}
GeckoTextMarkerRange::GeckoTextMarkerRange() {
if (a11y::IsCacheActive()) {
mLegacy = false;
mCachedTextMarkerRange = CachedTextMarkerRange();
} else {
mLegacy = true;
mLegacyTextMarkerRange = LegacyTextMarkerRange();
Accessible* GeckoTextMarker::Leaf() {
MOZ_ASSERT(mPoint.mAcc);
Accessible* acc = mPoint.mAcc;
if (mPoint.mOffset == 0) {
// If the marker is on the boundary between two leafs, MacOS expects the
// previous leaf.
TextLeafPoint prev = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
acc = prev.mAcc;
}
Accessible* parent = acc->Parent();
return parent && nsAccUtils::MustPrune(parent) ? parent : acc;
}
// GeckoTextMarkerRange
GeckoTextMarkerRange::GeckoTextMarkerRange(Accessible* aAccessible) {
mLegacy = !a11y::IsCacheActive();
if (mLegacy) {
mLegacyTextMarkerRange = LegacyTextMarkerRange(aAccessible);
} else {
mCachedTextMarkerRange = CachedTextMarkerRange(aAccessible);
}
mRange = TextLeafRange(
TextLeafPoint(aAccessible, 0),
TextLeafPoint(aAccessible, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
}
GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
@ -220,7 +347,6 @@ GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
GeckoTextMarker::MarkerFromAXTextMarker(aDoc, start_marker);
GeckoTextMarker end =
GeckoTextMarker::MarkerFromAXTextMarker(aDoc, end_marker);
MOZ_ASSERT(start.mLegacy == end.mLegacy);
CFRelease(start_marker);
CFRelease(end_marker);
@ -233,14 +359,8 @@ AXTextMarkerRangeRef GeckoTextMarkerRange::CreateAXTextMarkerRange() {
return nil;
}
GeckoTextMarker start, end;
if (mLegacy) {
start = GeckoTextMarker(mLegacyTextMarkerRange.mStart);
end = GeckoTextMarker(mLegacyTextMarkerRange.mEnd);
} else {
start = GeckoTextMarker(mCachedTextMarkerRange.mRange.Start());
end = GeckoTextMarker(mCachedTextMarkerRange.mRange.End());
}
GeckoTextMarker start = GeckoTextMarker(mRange.Start());
GeckoTextMarker end = GeckoTextMarker(mRange.End());
AXTextMarkerRangeRef cf_text_marker_range =
AXTextMarkerRangeCreate(kCFAllocatorDefault, start.CreateAXTextMarker(),
@ -250,5 +370,167 @@ AXTextMarkerRangeRef GeckoTextMarkerRange::CreateAXTextMarkerRange() {
cf_text_marker_range)autorelease];
}
NSString* GeckoTextMarkerRange::Text() const {
if (mRange.Start() == mRange.End()) {
return @"";
}
if ((mRange.Start().mAcc == mRange.End().mAcc) &&
(mRange.Start().mAcc->ChildCount() == 0) &&
(mRange.Start().mAcc->State() & states::EDITABLE)) {
return @"";
}
nsAutoString text;
TextLeafPoint prev = mRange.Start().FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
TextLeafRange range =
prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
: mRange;
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
continue;
}
start.mAcc->AppendTextTo(text, start.mOffset,
segment.End().mOffset - start.mOffset);
}
return nsCocoaUtils::ToNSString(text);
}
static void AppendTextToAttributedString(
NSMutableAttributedString* aAttributedString, Accessible* aAccessible,
const nsString& aString, AccAttributes* aAttributes) {
NSAttributedString* substr = [[[NSAttributedString alloc]
initWithString:nsCocoaUtils::ToNSString(aString)
attributes:utils::StringAttributesFromAccAttributes(
aAttributes, aAccessible)] autorelease];
[aAttributedString appendAttributedString:substr];
}
NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
NSMutableAttributedString* str =
[[[NSMutableAttributedString alloc] init] autorelease];
if (mRange.Start() == mRange.End()) {
return str;
}
if ((mRange.Start().mAcc == mRange.End().mAcc) &&
(mRange.Start().mAcc->ChildCount() == 0) &&
(mRange.Start().mAcc->IsTextField())) {
return str;
}
TextLeafPoint prev = mRange.Start().FindBoundary(
nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
TextLeafRange range =
prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
: mRange;
nsAutoString text;
RefPtr<AccAttributes> currentRun = nullptr;
Accessible* runAcc = range.Start().mAcc;
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
continue;
}
if (!currentRun) {
// This is the first segment that isn't an empty input.
currentRun = start.GetTextAttributes();
}
TextLeafPoint attributesNext;
do {
attributesNext = start.FindTextAttrsStart(eDirNext, false);
if (attributesNext == start) {
// XXX: FindTextAttrsStart should not return the same point.
break;
}
RefPtr<AccAttributes> attributes = start.GetTextAttributes();
MOZ_ASSERT(attributes);
if (attributes && !attributes->Equal(currentRun)) {
AppendTextToAttributedString(str, runAcc, text, currentRun);
text.Truncate();
currentRun = attributes;
runAcc = start.mAcc;
}
TextLeafPoint end =
attributesNext < segment.End() ? attributesNext : segment.End();
start.mAcc->AppendTextTo(text, start.mOffset,
end.mOffset - start.mOffset);
start = attributesNext;
} while (attributesNext < segment.End());
}
if (!text.IsEmpty()) {
AppendTextToAttributedString(str, runAcc, text, currentRun);
}
return str;
}
int32_t GeckoTextMarkerRange::Length() const {
int32_t length = 0;
for (TextLeafRange segment : mRange) {
if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
// XXX: MacOS expects bullets to be in the range's text, but not in
// the calculated length!
continue;
}
length += segment.End().mOffset - segment.Start().mOffset;
}
return length;
}
NSValue* GeckoTextMarkerRange::Bounds() const {
LayoutDeviceIntRect rect = mRange ? mRange.Bounds() : LayoutDeviceIntRect();
NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
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);
return [NSValue valueWithRect:r];
}
void GeckoTextMarkerRange::Select() const { mRange.SetSelection(0); }
bool GeckoTextMarkerRange::Crop(Accessible* aContainer) {
TextLeafPoint containerStart(aContainer, 0);
TextLeafPoint containerEnd(aContainer,
nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
if (mRange.End() < containerStart || containerEnd < mRange.Start()) {
// The range ends before the container, or starts after it.
return false;
}
if (mRange.Start() < containerStart) {
// If range start is before container start, adjust range start to
// start of container.
mRange.SetStart(containerStart);
}
if (containerEnd < mRange.End()) {
// If range end is after container end, adjust range end to end of
// container.
mRange.SetEnd(containerEnd);
}
return true;
}
} // namespace a11y
} // namespace mozilla

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

@ -1,119 +0,0 @@
/* clang-format off */
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* clang-format on */
/* 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 _LegacyTextMarker_H_
#define _LegacyTextMarker_H_
#include <ApplicationServices/ApplicationServices.h>
#include <Foundation/Foundation.h>
#include "HyperTextAccessibleWrap.h"
#include "PlatformExtTypes.h"
#include "SDKDeclarations.h"
namespace mozilla {
namespace a11y {
class Accessible;
class LegacyTextMarkerRange;
class LegacyTextMarker final {
public:
LegacyTextMarker(Accessible* aContainer, int32_t aOffset)
: mContainer(aContainer), mOffset(aOffset) {}
LegacyTextMarker(const LegacyTextMarker& aPoint)
: mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) {}
LegacyTextMarker() : mContainer(nullptr), mOffset(0) {}
static LegacyTextMarker MarkerFromIndex(Accessible* aRoot, int32_t aIndex);
bool Next();
bool Previous();
// Return a range with the given type relative to this marker.
LegacyTextMarkerRange Range(EWhichRange aRangeType) const;
Accessible* Leaf();
bool IsValid() const { return !!mContainer; };
bool operator<(const LegacyTextMarker& aPoint) const;
bool operator==(const LegacyTextMarker& aPoint) const {
return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset;
}
Accessible* mContainer;
int32_t mOffset;
HyperTextAccessibleWrap* ContainerAsHyperTextWrap() const {
return (mContainer && mContainer->IsLocal())
? static_cast<HyperTextAccessibleWrap*>(
mContainer->AsLocal()->AsHyperText())
: nullptr;
}
private:
bool IsEditableRoot();
};
class LegacyTextMarkerRange final {
public:
LegacyTextMarkerRange(const LegacyTextMarker& aStart,
const LegacyTextMarker& aEnd)
: mStart(aStart), mEnd(aEnd) {}
LegacyTextMarkerRange() {}
explicit LegacyTextMarkerRange(Accessible* aAccessible);
bool IsValid() const { return !!mStart.mContainer && !!mEnd.mContainer; };
/**
* Return text enclosed by the range.
*/
NSString* Text() const;
/**
* Return the attributed text enclosed by the range.
*/
NSAttributedString* AttributedText() const;
/**
* Return length of characters enclosed by the range.
*/
int32_t Length() const;
/**
* Return screen bounds of range.
*/
NSValue* Bounds() const;
/**
* Set the current range as the DOM selection.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY void Select() const;
/**
* Crops the range if it overlaps the given accessible element boundaries.
* Return true if successfully cropped. false if the range does not intersect
* with the container.
*/
bool Crop(Accessible* aContainer);
LegacyTextMarker mStart;
LegacyTextMarker mEnd;
};
} // namespace a11y
} // namespace mozilla
#endif

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

@ -1,391 +0,0 @@
/* clang-format off */
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* clang-format on */
/* 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/. */
#import "LegacyTextMarker.h"
#import "MacUtils.h"
#include "DocAccessible.h"
#include "DocAccessibleParent.h"
#include "AccAttributes.h"
#include "nsCocoaUtils.h"
#include "MOXAccessibleBase.h"
#include "mozAccessible.h"
#include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
namespace mozilla {
namespace a11y {
LegacyTextMarker LegacyTextMarker::MarkerFromIndex(Accessible* aRoot,
int32_t aIndex) {
if (aRoot->IsRemote()) {
int32_t offset = 0;
uint64_t containerID = 0;
DocAccessibleParent* ipcDoc = aRoot->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendOffsetAtIndex(
aRoot->AsRemote()->ID(), aIndex, &containerID, &offset);
RemoteAccessible* container = ipcDoc->GetAccessible(containerID);
return LegacyTextMarker(container, offset);
} else if (auto htWrap = static_cast<HyperTextAccessibleWrap*>(
aRoot->AsLocal()->AsHyperText())) {
int32_t offset = 0;
HyperTextAccessible* container = nullptr;
htWrap->OffsetAtIndex(aIndex, &container, &offset);
return LegacyTextMarker(container, offset);
}
return LegacyTextMarker();
}
bool LegacyTextMarker::operator<(const LegacyTextMarker& aPoint) const {
if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
// Build the chain of parents
AutoTArray<Accessible*, 30> parents1, parents2;
Accessible* p1 = mContainer;
while (p1) {
parents1.AppendElement(p1);
p1 = p1->Parent();
}
Accessible* p2 = aPoint.mContainer;
while (p2) {
parents2.AppendElement(p2);
p2 = p2->Parent();
}
// 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!");
// 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) {
Accessible* child1 = parents1.ElementAt(--pos1);
Accessible* child2 = parents2.ElementAt(--pos2);
if (child1 != child2) {
return child1->IndexInParent() < child2->IndexInParent();
}
}
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.
Accessible* child = parents1.ElementAt(pos1 - 1);
MOZ_ASSERT(child->Parent() == aPoint.mContainer);
uint32_t endOffset = child->EndOffset();
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.
Accessible* child = parents2.ElementAt(pos2 - 1);
MOZ_ASSERT(child->Parent() == mContainer);
uint32_t startOffset = child->StartOffset();
return static_cast<uint32_t>(mOffset) <= startOffset;
}
MOZ_ASSERT_UNREACHABLE("Broken tree?!");
return false;
}
bool LegacyTextMarker::IsEditableRoot() {
uint64_t state = mContainer->IsRemote() ? mContainer->AsRemote()->State()
: mContainer->AsLocal()->State();
if ((state & states::EDITABLE) == 0) {
return false;
}
Accessible* parent = mContainer->Parent();
if (!parent) {
// Not sure when this can happen, but it would technically be an editable
// root.
return true;
}
state = parent->IsRemote() ? parent->AsRemote()->State()
: parent->AsLocal()->State();
return (state & states::EDITABLE) == 0;
}
bool LegacyTextMarker::Next() {
if (mContainer->IsRemote()) {
int32_t nextOffset = 0;
uint64_t nextContainerID = 0;
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendNextClusterAt(
mContainer->AsRemote()->ID(), mOffset, &nextContainerID, &nextOffset);
RemoteAccessible* nextContainer = ipcDoc->GetAccessible(nextContainerID);
bool moved =
nextContainer != mContainer->AsRemote() || nextOffset != mOffset;
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;
}
return false;
}
bool LegacyTextMarker::Previous() {
if (mContainer->IsRemote()) {
int32_t prevOffset = 0;
uint64_t prevContainerID = 0;
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendPreviousClusterAt(
mContainer->AsRemote()->ID(), mOffset, &prevContainerID, &prevOffset);
RemoteAccessible* prevContainer = ipcDoc->GetAccessible(prevContainerID);
bool moved =
prevContainer != mContainer->AsRemote() || prevOffset != mOffset;
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;
}
return false;
}
static uint32_t CharacterCount(Accessible* aContainer) {
if (aContainer->IsRemote()) {
return aContainer->AsRemote()->CharacterCount();
}
if (aContainer->AsLocal()->IsHyperText()) {
return aContainer->AsLocal()->AsHyperText()->CharacterCount();
}
return 0;
}
LegacyTextMarkerRange LegacyTextMarker::Range(EWhichRange aRangeType) const {
MOZ_ASSERT(mContainer);
if (mContainer->IsRemote()) {
int32_t startOffset = 0, endOffset = 0;
uint64_t startContainerID = 0, endContainerID = 0;
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
bool success = ipcDoc->GetPlatformExtension()->SendRangeAt(
mContainer->AsRemote()->ID(), mOffset, aRangeType, &startContainerID,
&startOffset, &endContainerID, &endOffset);
if (success) {
return LegacyTextMarkerRange(
LegacyTextMarker(ipcDoc->GetAccessible(startContainerID),
startOffset),
LegacyTextMarker(ipcDoc->GetAccessible(endContainerID), endOffset));
}
} else if (auto htWrap = ContainerAsHyperTextWrap()) {
int32_t startOffset = 0, endOffset = 0;
HyperTextAccessible* startContainer = nullptr;
HyperTextAccessible* endContainer = nullptr;
htWrap->RangeAt(mOffset, aRangeType, &startContainer, &startOffset,
&endContainer, &endOffset);
return LegacyTextMarkerRange(LegacyTextMarker(startContainer, startOffset),
LegacyTextMarker(endContainer, endOffset));
}
return LegacyTextMarkerRange(LegacyTextMarker(), LegacyTextMarker());
}
Accessible* LegacyTextMarker::Leaf() {
MOZ_ASSERT(mContainer);
if (mContainer->IsRemote()) {
uint64_t leafID = 0;
DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendLeafAtOffset(
mContainer->AsRemote()->ID(), mOffset, &leafID);
return ipcDoc->GetAccessible(leafID);
} else if (auto htWrap = ContainerAsHyperTextWrap()) {
return htWrap->LeafAtOffset(mOffset);
}
return mContainer;
}
// LegacyTextMarkerRange
LegacyTextMarkerRange::LegacyTextMarkerRange(Accessible* aAccessible) {
if (aAccessible->IsHyperText()) {
// The accessible is a hypertext. Initialize range to its inner text range.
mStart = LegacyTextMarker(aAccessible, 0);
mEnd = LegacyTextMarker(aAccessible, (CharacterCount(aAccessible)));
} else {
// The accessible is not a hypertext (maybe a text leaf?). Initialize range
// to its offsets in its container.
mStart = LegacyTextMarker(aAccessible->Parent(), 0);
mEnd = LegacyTextMarker(aAccessible->Parent(), 0);
if (mStart.mContainer->IsRemote()) {
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendRangeOfChild(
mStart.mContainer->AsRemote()->ID(), aAccessible->AsRemote()->ID(),
&mStart.mOffset, &mEnd.mOffset);
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
htWrap->RangeOfChild(aAccessible->AsLocal(), &mStart.mOffset,
&mEnd.mOffset);
}
}
}
NSString* LegacyTextMarkerRange::Text() const {
nsAutoString text;
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendTextForRange(
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &text);
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
htWrap->TextForRange(text, mStart.mOffset, mEnd.ContainerAsHyperTextWrap(),
mEnd.mOffset);
}
return nsCocoaUtils::ToNSString(text);
}
NSAttributedString* LegacyTextMarkerRange::AttributedText() const {
NSMutableAttributedString* str =
[[[NSMutableAttributedString alloc] init] autorelease];
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
nsTArray<TextAttributesRun> textAttributesRuns;
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendAttributedTextForRange(
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &textAttributesRuns);
for (size_t i = 0; i < textAttributesRuns.Length(); i++) {
AccAttributes* attributes =
textAttributesRuns.ElementAt(i).TextAttributes();
RemoteAccessible* container =
ipcDoc->GetAccessible(textAttributesRuns.ElementAt(i).ContainerID());
NSAttributedString* substr = [[[NSAttributedString alloc]
initWithString:nsCocoaUtils::ToNSString(
textAttributesRuns.ElementAt(i).Text())
attributes:utils::StringAttributesFromAccAttributes(
attributes, container)] autorelease];
[str appendAttributedString:substr];
}
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
nsTArray<nsString> texts;
nsTArray<LocalAccessible*> containers;
nsTArray<RefPtr<AccAttributes>> props;
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:utils::StringAttributesFromAccAttributes(
props.ElementAt(i), containers.ElementAt(i))]
autorelease];
[str appendAttributedString:substr];
}
}
return str;
}
int32_t LegacyTextMarkerRange::Length() const {
int32_t length = 0;
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendLengthForRange(
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &length);
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
length = htWrap->LengthForRange(
mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
}
return length;
}
NSValue* LegacyTextMarkerRange::Bounds() const {
LayoutDeviceIntRect rect;
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendBoundsForRange(
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &rect);
} else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
rect = htWrap->BoundsForRange(
mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
}
NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
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);
return [NSValue valueWithRect:r];
}
void LegacyTextMarkerRange::Select() const {
if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
Unused << ipcDoc->GetPlatformExtension()->SendSelectRange(
mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset);
} else if (RefPtr<HyperTextAccessibleWrap> htWrap =
mStart.ContainerAsHyperTextWrap()) {
RefPtr<HyperTextAccessibleWrap> end = mEnd.ContainerAsHyperTextWrap();
htWrap->SelectRange(mStart.mOffset, end, mEnd.mOffset);
}
}
bool LegacyTextMarkerRange::Crop(Accessible* aContainer) {
LegacyTextMarkerRange containerRange(aContainer);
if (mEnd < containerRange.mStart || containerRange.mEnd < mStart) {
// The range ends before the container, or starts after it.
return false;
}
if (mStart < containerRange.mStart) {
// If range start is before container start, adjust range start to
// start of container.
mStart = containerRange.mStart;
}
if (containerRange.mEnd < mEnd) {
// If range end is after container end, adjust range end to end of
// container.
mEnd = containerRange.mEnd;
}
return true;
}
} // namespace a11y
} // namespace mozilla

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

@ -16,11 +16,9 @@ EXPORTS.mozilla.a11y += [
UNIFIED_SOURCES += [
"AccessibleWrap.mm",
"CachedTextMarker.mm",
"DocAccessibleWrap.mm",
"GeckoTextMarker.mm",
"HyperTextAccessibleWrap.mm",
"LegacyTextMarker.mm",
"MacUtils.mm",
"MOXAccessibleBase.mm",
"MOXLandmarkAccessibles.mm",