зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
2237be2626
Коммит
de34976841
|
@ -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_
|
#ifndef _GeckoTextMarker_H_
|
||||||
#define _GeckoTextMarker_H_
|
#define _GeckoTextMarker_H_
|
||||||
|
|
||||||
|
#include <ApplicationServices/ApplicationServices.h>
|
||||||
#include <Foundation/Foundation.h>
|
#include <Foundation/Foundation.h>
|
||||||
#import "LegacyTextMarker.h"
|
|
||||||
#import "CachedTextMarker.h"
|
#include "TextLeafRange.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace a11y {
|
namespace a11y {
|
||||||
|
@ -19,29 +20,14 @@ namespace a11y {
|
||||||
class Accessible;
|
class Accessible;
|
||||||
class GeckoTextMarkerRange;
|
class GeckoTextMarkerRange;
|
||||||
|
|
||||||
class GeckoTextMarker {
|
class GeckoTextMarker final {
|
||||||
public:
|
public:
|
||||||
GeckoTextMarker();
|
GeckoTextMarker(Accessible* aAcc, int32_t aOffset);
|
||||||
|
|
||||||
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) {}
|
|
||||||
|
|
||||||
explicit GeckoTextMarker(const TextLeafPoint& aTextLeafPoint)
|
explicit GeckoTextMarker(const TextLeafPoint& aTextLeafPoint)
|
||||||
: mLegacy(false), mCachedTextMarker(aTextLeafPoint) {}
|
: mPoint(aTextLeafPoint) {}
|
||||||
|
|
||||||
GeckoTextMarker(Accessible* aContainer, int32_t aOffset);
|
GeckoTextMarker() : mPoint() {}
|
||||||
|
|
||||||
static GeckoTextMarker MarkerFromAXTextMarker(Accessible* aDoc,
|
static GeckoTextMarker MarkerFromAXTextMarker(Accessible* aDoc,
|
||||||
AXTextMarkerRef aTextMarker);
|
AXTextMarkerRef aTextMarker);
|
||||||
|
@ -50,14 +36,9 @@ class GeckoTextMarker {
|
||||||
|
|
||||||
AXTextMarkerRef CreateAXTextMarker();
|
AXTextMarkerRef CreateAXTextMarker();
|
||||||
|
|
||||||
bool Next() {
|
bool Next();
|
||||||
return mLegacy ? mLegacyTextMarker.Next() : mCachedTextMarker.Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Previous() {
|
bool Previous();
|
||||||
return mLegacy ? mLegacyTextMarker.Previous()
|
|
||||||
: mCachedTextMarker.Previous();
|
|
||||||
}
|
|
||||||
|
|
||||||
GeckoTextMarkerRange LeftWordRange() const;
|
GeckoTextMarkerRange LeftWordRange() const;
|
||||||
|
|
||||||
|
@ -73,75 +54,35 @@ class GeckoTextMarker {
|
||||||
|
|
||||||
GeckoTextMarkerRange StyleRange() const;
|
GeckoTextMarkerRange StyleRange() const;
|
||||||
|
|
||||||
Accessible* Leaf() {
|
int32_t& Offset() { return mPoint.mOffset; }
|
||||||
return mLegacy ? mLegacyTextMarker.Leaf() : mCachedTextMarker.Leaf();
|
|
||||||
|
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() {
|
bool operator==(const GeckoTextMarker& aOther) const {
|
||||||
return mLegacy ? mLegacyTextMarker.mOffset
|
return mPoint == aOther.mPoint;
|
||||||
: mCachedTextMarker.mPoint.mOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Accessible* Acc() const {
|
TextLeafPoint mPoint;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GeckoTextMarkerRange {
|
class GeckoTextMarkerRange final {
|
||||||
public:
|
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,
|
GeckoTextMarkerRange(const GeckoTextMarker& aStart,
|
||||||
const GeckoTextMarker& aEnd) {
|
const GeckoTextMarker& aEnd)
|
||||||
MOZ_ASSERT(aStart.mLegacy == aEnd.mLegacy);
|
: mRange(aStart.mPoint, aEnd.mPoint) {}
|
||||||
mLegacy = aStart.mLegacy;
|
|
||||||
if (mLegacy) {
|
GeckoTextMarkerRange(const TextLeafPoint& aStart, const TextLeafPoint& aEnd)
|
||||||
mLegacyTextMarkerRange = LegacyTextMarkerRange(aStart.mLegacyTextMarker,
|
: mRange(aStart, aEnd) {}
|
||||||
aEnd.mLegacyTextMarker);
|
|
||||||
} else {
|
GeckoTextMarkerRange() {}
|
||||||
mCachedTextMarkerRange = CachedTextMarkerRange(aStart.mCachedTextMarker,
|
|
||||||
aEnd.mCachedTextMarker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit GeckoTextMarkerRange(Accessible* aAccessible);
|
explicit GeckoTextMarkerRange(Accessible* aAccessible);
|
||||||
|
|
||||||
|
@ -150,76 +91,45 @@ class GeckoTextMarkerRange {
|
||||||
|
|
||||||
AXTextMarkerRangeRef CreateAXTextMarkerRange();
|
AXTextMarkerRangeRef CreateAXTextMarkerRange();
|
||||||
|
|
||||||
bool IsValid() const {
|
bool IsValid() const { return !!mRange.Start() && !!mRange.End(); };
|
||||||
return mLegacy ? mLegacyTextMarkerRange.IsValid()
|
|
||||||
: mCachedTextMarkerRange.IsValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
GeckoTextMarker Start() {
|
GeckoTextMarker Start() { return GeckoTextMarker(mRange.Start()); }
|
||||||
return mLegacy ? GeckoTextMarker(mLegacyTextMarkerRange.mStart)
|
|
||||||
: GeckoTextMarker(mCachedTextMarkerRange.mRange.Start());
|
|
||||||
}
|
|
||||||
|
|
||||||
GeckoTextMarker End() {
|
GeckoTextMarker End() { return GeckoTextMarker(mRange.End()); }
|
||||||
return mLegacy ? GeckoTextMarker(mLegacyTextMarkerRange.mEnd)
|
|
||||||
: GeckoTextMarker(mCachedTextMarkerRange.mRange.End());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return text enclosed by the range.
|
* Return text enclosed by the range.
|
||||||
*/
|
*/
|
||||||
NSString* Text() const {
|
NSString* Text() const;
|
||||||
return mLegacy ? mLegacyTextMarkerRange.Text()
|
|
||||||
: mCachedTextMarkerRange.Text();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the attributed text enclosed by the range.
|
* Return the attributed text enclosed by the range.
|
||||||
*/
|
*/
|
||||||
NSAttributedString* AttributedText() const {
|
NSAttributedString* AttributedText() const;
|
||||||
return mLegacy ? mLegacyTextMarkerRange.AttributedText()
|
|
||||||
: mCachedTextMarkerRange.AttributedText();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return length of characters enclosed by the range.
|
* Return length of characters enclosed by the range.
|
||||||
*/
|
*/
|
||||||
int32_t Length() const {
|
int32_t Length() const;
|
||||||
return mLegacy ? mLegacyTextMarkerRange.Length()
|
|
||||||
: mCachedTextMarkerRange.Length();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return screen bounds of range.
|
* Return screen bounds of range.
|
||||||
*/
|
*/
|
||||||
NSValue* Bounds() const {
|
NSValue* Bounds() const;
|
||||||
return mLegacy ? mLegacyTextMarkerRange.Bounds()
|
|
||||||
: mCachedTextMarkerRange.Bounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the current range as the DOM selection.
|
* Set the current range as the DOM selection.
|
||||||
*/
|
*/
|
||||||
void Select() const {
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY void Select() const;
|
||||||
mLegacy ? mLegacyTextMarkerRange.Select() : mCachedTextMarkerRange.Select();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crops the range if it overlaps the given accessible element boundaries.
|
* Crops the range if it overlaps the given accessible element boundaries.
|
||||||
* Return true if successfully cropped. false if the range does not intersect
|
* Return true if successfully cropped. false if the range does not intersect
|
||||||
* with the container.
|
* with the container.
|
||||||
*/
|
*/
|
||||||
bool Crop(Accessible* aContainer) {
|
bool Crop(Accessible* aContainer);
|
||||||
return mLegacy ? mLegacyTextMarkerRange.Crop(aContainer)
|
|
||||||
: mCachedTextMarkerRange.Crop(aContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
TextLeafRange mRange;
|
||||||
bool mLegacy;
|
|
||||||
union {
|
|
||||||
LegacyTextMarkerRange mLegacyTextMarkerRange;
|
|
||||||
CachedTextMarkerRange mCachedTextMarkerRange;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace a11y
|
} // namespace a11y
|
||||||
|
|
|
@ -9,51 +9,34 @@
|
||||||
|
|
||||||
#import "MacUtils.h"
|
#import "MacUtils.h"
|
||||||
|
|
||||||
#include "nsAccUtils.h"
|
#include "AccAttributes.h"
|
||||||
#include "DocAccessible.h"
|
|
||||||
#include "DocAccessibleParent.h"
|
#include "DocAccessibleParent.h"
|
||||||
#include "nsAccessibilityService.h"
|
#include "nsCocoaUtils.h"
|
||||||
|
#include "HyperTextAccessible.h"
|
||||||
|
#include "States.h"
|
||||||
|
#include "nsAccUtils.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace a11y {
|
namespace a11y {
|
||||||
|
|
||||||
struct TextMarkerData {
|
struct TextMarkerData {
|
||||||
TextMarkerData(uintptr_t aDoc, uintptr_t aID, int32_t aOffset, bool aLegacy)
|
TextMarkerData(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
|
||||||
: mDoc(aDoc), mID(aID), mOffset(aOffset), mLegacy(aLegacy) {}
|
: mDoc(aDoc), mID(aID), mOffset(aOffset) {}
|
||||||
TextMarkerData() {}
|
TextMarkerData() {}
|
||||||
uintptr_t mDoc;
|
uintptr_t mDoc;
|
||||||
uintptr_t mID;
|
uintptr_t mID;
|
||||||
int32_t mOffset;
|
int32_t mOffset;
|
||||||
bool mLegacy;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// LegacyTextMarker
|
// GeckoTextMarker
|
||||||
|
|
||||||
GeckoTextMarker::GeckoTextMarker() {
|
GeckoTextMarker::GeckoTextMarker(Accessible* aAcc, int32_t aOffset) {
|
||||||
if (a11y::IsCacheActive()) {
|
HyperTextAccessibleBase* ht = aAcc->AsHyperTextBase();
|
||||||
mLegacy = false;
|
if (ht && aOffset != nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
|
||||||
mCachedTextMarker = CachedTextMarker();
|
aOffset <= static_cast<int32_t>(ht->CharacterCount())) {
|
||||||
|
mPoint = aAcc->AsHyperTextBase()->ToTextLeafPoint(aOffset);
|
||||||
} else {
|
} else {
|
||||||
mLegacy = true;
|
mPoint = TextLeafPoint(aAcc, aOffset);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +55,6 @@ GeckoTextMarker GeckoTextMarker::MarkerFromAXTextMarker(
|
||||||
TextMarkerData markerData;
|
TextMarkerData markerData;
|
||||||
memcpy(&markerData, AXTextMarkerGetBytePtr(aTextMarker),
|
memcpy(&markerData, AXTextMarkerGetBytePtr(aTextMarker),
|
||||||
sizeof(TextMarkerData));
|
sizeof(TextMarkerData));
|
||||||
MOZ_ASSERT(a11y::IsCacheActive() == !markerData.mLegacy);
|
|
||||||
|
|
||||||
if (!utils::DocumentExists(aDoc, markerData.mDoc)) {
|
if (!utils::DocumentExists(aDoc, markerData.mDoc)) {
|
||||||
return GeckoTextMarker();
|
return GeckoTextMarker();
|
||||||
|
@ -98,11 +80,28 @@ GeckoTextMarker GeckoTextMarker::MarkerFromAXTextMarker(
|
||||||
|
|
||||||
GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
|
GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
|
||||||
int32_t aIndex) {
|
int32_t aIndex) {
|
||||||
if (a11y::IsCacheActive()) {
|
TextLeafRange range(
|
||||||
return GeckoTextMarker(CachedTextMarker::MarkerFromIndex(aRoot, aIndex));
|
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() {
|
AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
|
||||||
|
@ -110,13 +109,9 @@ AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
Accessible* acc =
|
Accessible* doc = nsAccUtils::DocumentFor(mPoint.mAcc);
|
||||||
mLegacy ? mLegacyTextMarker.mContainer : mCachedTextMarker.mPoint.mAcc;
|
TextMarkerData markerData(reinterpret_cast<uintptr_t>(doc), mPoint.mAcc->ID(),
|
||||||
int32_t offset =
|
mPoint.mOffset);
|
||||||
mLegacy ? mLegacyTextMarker.mOffset : mCachedTextMarker.mPoint.mOffset;
|
|
||||||
Accessible* doc = nsAccUtils::DocumentFor(acc);
|
|
||||||
TextMarkerData markerData(reinterpret_cast<uintptr_t>(doc), acc->ID(), offset,
|
|
||||||
mLegacy);
|
|
||||||
AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
|
AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
|
||||||
kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&markerData),
|
kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&markerData),
|
||||||
sizeof(TextMarkerData));
|
sizeof(TextMarkerData));
|
||||||
|
@ -124,85 +119,217 @@ AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
|
||||||
return (__bridge AXTextMarkerRef)[(__bridge id)(cf_text_marker)autorelease];
|
return (__bridge AXTextMarkerRef)[(__bridge id)(cf_text_marker)autorelease];
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoTextMarkerRange GeckoTextMarker::LeftWordRange() const {
|
bool GeckoTextMarker::Next() {
|
||||||
if (mLegacy) {
|
TextLeafPoint next =
|
||||||
return GeckoTextMarkerRange(
|
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext,
|
||||||
mLegacyTextMarker.Range(EWhichRange::eLeftWord));
|
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
|
||||||
} else {
|
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.LeftWordRange());
|
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 {
|
GeckoTextMarkerRange GeckoTextMarker::RightWordRange() const {
|
||||||
if (mLegacy) {
|
TextLeafPoint prevChar =
|
||||||
return GeckoTextMarkerRange(
|
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
|
||||||
mLegacyTextMarker.Range(EWhichRange::eRightWord));
|
TextLeafPoint::BoundaryFlags::eStopInEditable);
|
||||||
} else {
|
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.RightWordRange());
|
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 {
|
GeckoTextMarkerRange GeckoTextMarker::LineRange() const {
|
||||||
if (mLegacy) {
|
TextLeafPoint start = mPoint.FindBoundary(
|
||||||
return GeckoTextMarkerRange(
|
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
|
||||||
mLegacyTextMarker.Range(EWhichRange::eLeftLine));
|
TextLeafPoint::BoundaryFlags::eStopInEditable |
|
||||||
} else {
|
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker |
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.LineRange());
|
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 {
|
GeckoTextMarkerRange GeckoTextMarker::LeftLineRange() const {
|
||||||
if (mLegacy) {
|
TextLeafPoint start = mPoint.FindBoundary(
|
||||||
return GeckoTextMarkerRange(
|
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
|
||||||
mLegacyTextMarker.Range(EWhichRange::eLeftLine));
|
TextLeafPoint::BoundaryFlags::eStopInEditable |
|
||||||
} else {
|
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.LeftLineRange());
|
TextLeafPoint end =
|
||||||
}
|
start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
|
||||||
|
TextLeafPoint::BoundaryFlags::eStopInEditable);
|
||||||
|
|
||||||
|
return GeckoTextMarkerRange(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoTextMarkerRange GeckoTextMarker::RightLineRange() const {
|
GeckoTextMarkerRange GeckoTextMarker::RightLineRange() const {
|
||||||
if (mLegacy) {
|
TextLeafPoint end =
|
||||||
return GeckoTextMarkerRange(
|
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
|
||||||
mLegacyTextMarker.Range(EWhichRange::eRightLine));
|
TextLeafPoint::BoundaryFlags::eStopInEditable);
|
||||||
} else {
|
TextLeafPoint start =
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.RightLineRange());
|
end.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
|
||||||
}
|
TextLeafPoint::BoundaryFlags::eStopInEditable);
|
||||||
|
|
||||||
|
return GeckoTextMarkerRange(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoTextMarkerRange GeckoTextMarker::ParagraphRange() const {
|
GeckoTextMarkerRange GeckoTextMarker::ParagraphRange() const {
|
||||||
if (mLegacy) {
|
// XXX: WebKit gets trapped in inputs. Maybe we shouldn't?
|
||||||
return GeckoTextMarkerRange(
|
TextLeafPoint end =
|
||||||
mLegacyTextMarker.Range(EWhichRange::eParagraph));
|
mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirNext,
|
||||||
} else {
|
TextLeafPoint::BoundaryFlags::eStopInEditable);
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.ParagraphRange());
|
TextLeafPoint start =
|
||||||
}
|
end.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirPrevious,
|
||||||
|
TextLeafPoint::BoundaryFlags::eStopInEditable);
|
||||||
|
|
||||||
|
return GeckoTextMarkerRange(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoTextMarkerRange GeckoTextMarker::StyleRange() const {
|
GeckoTextMarkerRange GeckoTextMarker::StyleRange() const {
|
||||||
if (mLegacy) {
|
if (mPoint.mOffset == 0) {
|
||||||
return GeckoTextMarkerRange(mLegacyTextMarker.Range(EWhichRange::eStyle));
|
// If the marker is on the boundary between two leafs, MacOS expects the
|
||||||
} else {
|
// previous leaf.
|
||||||
return GeckoTextMarkerRange(mCachedTextMarker.StyleRange());
|
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() {
|
Accessible* GeckoTextMarker::Leaf() {
|
||||||
if (a11y::IsCacheActive()) {
|
MOZ_ASSERT(mPoint.mAcc);
|
||||||
mLegacy = false;
|
Accessible* acc = mPoint.mAcc;
|
||||||
mCachedTextMarkerRange = CachedTextMarkerRange();
|
if (mPoint.mOffset == 0) {
|
||||||
} else {
|
// If the marker is on the boundary between two leafs, MacOS expects the
|
||||||
mLegacy = true;
|
// previous leaf.
|
||||||
mLegacyTextMarkerRange = LegacyTextMarkerRange();
|
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) {
|
GeckoTextMarkerRange::GeckoTextMarkerRange(Accessible* aAccessible) {
|
||||||
mLegacy = !a11y::IsCacheActive();
|
mRange = TextLeafRange(
|
||||||
if (mLegacy) {
|
TextLeafPoint(aAccessible, 0),
|
||||||
mLegacyTextMarkerRange = LegacyTextMarkerRange(aAccessible);
|
TextLeafPoint(aAccessible, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
|
||||||
} else {
|
|
||||||
mCachedTextMarkerRange = CachedTextMarkerRange(aAccessible);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
|
GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
|
||||||
|
@ -220,7 +347,6 @@ GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
|
||||||
GeckoTextMarker::MarkerFromAXTextMarker(aDoc, start_marker);
|
GeckoTextMarker::MarkerFromAXTextMarker(aDoc, start_marker);
|
||||||
GeckoTextMarker end =
|
GeckoTextMarker end =
|
||||||
GeckoTextMarker::MarkerFromAXTextMarker(aDoc, end_marker);
|
GeckoTextMarker::MarkerFromAXTextMarker(aDoc, end_marker);
|
||||||
MOZ_ASSERT(start.mLegacy == end.mLegacy);
|
|
||||||
|
|
||||||
CFRelease(start_marker);
|
CFRelease(start_marker);
|
||||||
CFRelease(end_marker);
|
CFRelease(end_marker);
|
||||||
|
@ -233,14 +359,8 @@ AXTextMarkerRangeRef GeckoTextMarkerRange::CreateAXTextMarkerRange() {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoTextMarker start, end;
|
GeckoTextMarker start = GeckoTextMarker(mRange.Start());
|
||||||
if (mLegacy) {
|
GeckoTextMarker end = GeckoTextMarker(mRange.End());
|
||||||
start = GeckoTextMarker(mLegacyTextMarkerRange.mStart);
|
|
||||||
end = GeckoTextMarker(mLegacyTextMarkerRange.mEnd);
|
|
||||||
} else {
|
|
||||||
start = GeckoTextMarker(mCachedTextMarkerRange.mRange.Start());
|
|
||||||
end = GeckoTextMarker(mCachedTextMarkerRange.mRange.End());
|
|
||||||
}
|
|
||||||
|
|
||||||
AXTextMarkerRangeRef cf_text_marker_range =
|
AXTextMarkerRangeRef cf_text_marker_range =
|
||||||
AXTextMarkerRangeCreate(kCFAllocatorDefault, start.CreateAXTextMarker(),
|
AXTextMarkerRangeCreate(kCFAllocatorDefault, start.CreateAXTextMarker(),
|
||||||
|
@ -250,5 +370,167 @@ AXTextMarkerRangeRef GeckoTextMarkerRange::CreateAXTextMarkerRange() {
|
||||||
cf_text_marker_range)autorelease];
|
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 a11y
|
||||||
} // namespace mozilla
|
} // 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 += [
|
UNIFIED_SOURCES += [
|
||||||
"AccessibleWrap.mm",
|
"AccessibleWrap.mm",
|
||||||
"CachedTextMarker.mm",
|
|
||||||
"DocAccessibleWrap.mm",
|
"DocAccessibleWrap.mm",
|
||||||
"GeckoTextMarker.mm",
|
"GeckoTextMarker.mm",
|
||||||
"HyperTextAccessibleWrap.mm",
|
"HyperTextAccessibleWrap.mm",
|
||||||
"LegacyTextMarker.mm",
|
|
||||||
"MacUtils.mm",
|
"MacUtils.mm",
|
||||||
"MOXAccessibleBase.mm",
|
"MOXAccessibleBase.mm",
|
||||||
"MOXLandmarkAccessibles.mm",
|
"MOXLandmarkAccessibles.mm",
|
||||||
|
|
Загрузка…
Ссылка в новой задаче