зеркало из 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_
|
||||
#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",
|
||||
|
|
Загрузка…
Ссылка в новой задаче