1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TextRange-inl.h"
#include "LocalAccessible-inl.h"
#include "HyperTextAccessible-inl.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/dom/Selection.h"
#include "nsAccUtils.h"
namespace mozilla {
namespace a11y {
/**
* Returns a text point for aAcc within aContainer.
*/
static void ToTextPoint(Accessible* aAcc, Accessible** aContainer,
int32_t* aOffset, bool aIsBefore = true) {
if (aAcc->IsHyperText()) {
*aContainer = aAcc;
*aOffset =
aIsBefore
? 0
: static_cast<int32_t>(aAcc->AsHyperTextBase()->CharacterCount());
return;
}
Accessible* child = nullptr;
Accessible* parent = aAcc;
do {
child = parent;
parent = parent->Parent();
} while (parent && !parent->IsHyperText());
if (parent) {
*aContainer = parent;
*aOffset = parent->AsHyperTextBase()->GetChildOffset(
child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
}
}
////////////////////////////////////////////////////////////////////////////////
// TextPoint
bool TextPoint::operator<(const TextPoint& aPoint) const {
if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
// Build the chain of parents
Accessible* p1 = mContainer;
Accessible* p2 = aPoint.mContainer;
AutoTArray<Accessible*, 30> parents1, parents2;
do {
parents1.AppendElement(p1);
p1 = p1->Parent();
} while (p1);
do {
parents2.AppendElement(p2);
p2 = p2->Parent();
} while (p2);
// 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);
// If the offsets are equal, aPoint points to an ancestor embedded object
// for this. aPoint should be treated as earlier in the text. This is why we
// use <= here.
return child->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);
// If the offsets are equal, aPoint points to an ancestor embedded object
// for this. aPoint should be treated as earlier in the text. This is why we
// use <= here.
return static_cast<uint32_t>(mOffset) <= child->StartOffset();
}
NS_ERROR("Broken tree?!");
return false;
}
////////////////////////////////////////////////////////////////////////////////
// TextRange
TextRange::TextRange(Accessible* aRoot, Accessible* aStartContainer,
int32_t aStartOffset, Accessible* aEndContainer,
int32_t aEndOffset)
: mRoot(aRoot),
mStartContainer(aStartContainer),
mEndContainer(aEndContainer),
mStartOffset(aStartOffset),
mEndOffset(aEndOffset) {}
bool TextRange::Crop(Accessible* aContainer) {
uint32_t boundaryPos = 0, containerPos = 0;
AutoTArray<Accessible*, 30> boundaryParents, containerParents;
// Crop the start boundary.
Accessible* container = nullptr;
HyperTextAccessibleBase* startHyper = mStartContainer->AsHyperTextBase();
Accessible* boundary = startHyper->GetChildAtOffset(mStartOffset);
if (!boundary) {
// mStartContainer is empty.
MOZ_ASSERT(mStartOffset == 0 && startHyper->CharacterCount() == 0);
boundary = mStartContainer;
}
if (boundary != aContainer) {
CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
&containerParents, &containerPos);
if (boundaryPos == 0) {
if (containerPos != 0) {
// The container is contained by the start boundary, reduce the range to
// the point starting at the container.
ToTextPoint(aContainer, &mStartContainer, &mStartOffset);
} else {
// The start boundary and the container are siblings.
container = aContainer;
}
} else {
// The container does not contain the start boundary.
boundary = boundaryParents[boundaryPos];
container = containerParents[containerPos];
}
if (container) {
// If the range start is after the container, then make the range invalid.
if (boundary->IndexInParent() > container->IndexInParent()) {
return !!(mRoot = nullptr);
}
// If the range starts before the container, then reduce the range to
// the point starting at the container.
if (boundary->IndexInParent() < container->IndexInParent()) {
ToTextPoint(container, &mStartContainer, &mStartOffset);
}
}
boundaryParents.SetLengthAndRetainStorage(0);
containerParents.SetLengthAndRetainStorage(0);
}
HyperTextAccessibleBase* endHyper = mEndContainer->AsHyperTextBase();
boundary = endHyper->GetChildAtOffset(mEndOffset);
if (!boundary) {
// mEndContainer is empty.
MOZ_ASSERT(mEndOffset == 0 && endHyper->CharacterCount() == 0);
boundary = mEndContainer;
}
if (boundary == aContainer) {
return true;
}
// Crop the end boundary.
container = nullptr;
CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
&containerParents, &containerPos);
if (boundaryPos == 0) {
if (containerPos != 0) {
ToTextPoint(aContainer, &mEndContainer, &mEndOffset, false);
} else {
container = aContainer;
}
} else {
boundary = boundaryParents[boundaryPos];
container = containerParents[containerPos];
}
if (!container) {
return true;
}
if (boundary->IndexInParent() < container->IndexInParent()) {
return !!(mRoot = nullptr);
}
if (boundary->IndexInParent() > container->IndexInParent()) {
ToTextPoint(container, &mEndContainer, &mEndOffset, false);
}
return true;
}
/**
* Convert the given DOM point to a DOM point in non-generated contents.
*
* If aDOMPoint is in ::before, the result is immediately after it.
* If aDOMPoint is in ::after, the result is immediately before it.
*/
static DOMPoint ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
nsIContent* aElementContent) {
MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
// ::before pseudo element
if (aElementContent &&
aElementContent->IsGeneratedContentContainerForBefore()) {
MOZ_ASSERT(aElementContent->GetParent(),
"::before must have parent element");
// The first child of its parent (i.e., immediately after the ::before) is
// good point for a DOM range.
return DOMPoint(aElementContent->GetParent(), 0);
}
// ::after pseudo element
if (aElementContent &&
aElementContent->IsGeneratedContentContainerForAfter()) {
MOZ_ASSERT(aElementContent->GetParent(),
"::after must have parent element");
// The end of its parent (i.e., immediately before the ::after) is good
// point for a DOM range.
return DOMPoint(aElementContent->GetParent(),
aElementContent->GetParent()->GetChildCount());
}
return aDOMPoint;
}
/**
* GetElementAsContentOf() returns a content representing an element which is
* or includes aNode.
*
* XXX This method is enough to retrieve ::before or ::after pseudo element.
* So, if you want to use this for other purpose, you might need to check
* ancestors too.
*/
static nsIContent* GetElementAsContentOf(nsINode* aNode) {
if (auto* element = dom::Element::FromNode(aNode)) {
return element;
}
return aNode->GetParentElement();
}
bool TextRange::AssignDOMRange(nsRange* aRange, bool* aReversed) const {
MOZ_ASSERT(mRoot->IsLocal(), "Not supported for RemoteAccessible");
bool reversed = EndPoint() < StartPoint();
if (aReversed) {
*aReversed = reversed;
}
HyperTextAccessible* startHyper = mStartContainer->AsLocal()->AsHyperText();
HyperTextAccessible* endHyper = mEndContainer->AsLocal()->AsHyperText();
DOMPoint startPoint = reversed ? endHyper->OffsetToDOMPoint(mEndOffset)
: startHyper->OffsetToDOMPoint(mStartOffset);
if (!startPoint.node) {
return false;
}
// HyperTextAccessible manages pseudo elements generated by ::before or
// ::after. However, contents of them are not in the DOM tree normally.
// Therefore, they are not selectable and editable. So, when this creates
// a DOM range, it should not start from nor end in any pseudo contents.
nsIContent* container = GetElementAsContentOf(startPoint.node);
DOMPoint startPointForDOMRange =
ClosestNotGeneratedDOMPoint(startPoint, container);
aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
// If the caller wants collapsed range, let's collapse the range to its start.
if (mEndContainer == mStartContainer && mEndOffset == mStartOffset) {
aRange->Collapse(true);
return true;
}
DOMPoint endPoint = reversed ? startHyper->OffsetToDOMPoint(mStartOffset)
: endHyper->OffsetToDOMPoint(mEndOffset);
if (!endPoint.node) {
return false;
}
if (startPoint.node != endPoint.node) {
container = GetElementAsContentOf(endPoint.node);
}
DOMPoint endPointForDOMRange =
ClosestNotGeneratedDOMPoint(endPoint, container);
aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
return true;
}
void TextRange::TextRangesFromSelection(dom::Selection* aSelection,
nsTArray<TextRange>* aRanges) {
MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
aRanges->SetCapacity(aSelection->RangeCount());
const uint32_t rangeCount = aSelection->RangeCount();
for (const uint32_t idx : IntegerRange(rangeCount)) {
MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
const nsRange* DOMRange = aSelection->GetRangeAt(idx);
MOZ_ASSERT(DOMRange);
HyperTextAccessible* startContainer =
nsAccUtils::GetTextContainer(DOMRange->GetStartContainer());
HyperTextAccessible* endContainer =
nsAccUtils::GetTextContainer(DOMRange->GetEndContainer());
HyperTextAccessible* commonAncestor = nsAccUtils::GetTextContainer(
DOMRange->GetClosestCommonInclusiveAncestor());
if (!startContainer || !endContainer) {
continue;
}
int32_t startOffset = startContainer->DOMPointToOffset(
DOMRange->GetStartContainer(), DOMRange->StartOffset(), false);
int32_t endOffset = endContainer->DOMPointToOffset(
DOMRange->GetEndContainer(), DOMRange->EndOffset(), true);
TextRange tr(commonAncestor && commonAncestor->IsTextField()
? commonAncestor
: startContainer->Document(),
startContainer, startOffset, endContainer, endOffset);
*(aRanges->AppendElement()) = std::move(tr);
}
}
////////////////////////////////////////////////////////////////////////////////
// pivate
void TextRange::Set(Accessible* aRoot, Accessible* aStartContainer,
int32_t aStartOffset, Accessible* aEndContainer,
int32_t aEndOffset) {
mRoot = aRoot;
mStartContainer = aStartContainer;
mEndContainer = aEndContainer;
mStartOffset = aStartOffset;
mEndOffset = aEndOffset;
}
Accessible* TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
nsTArray<Accessible*>* aParents1,
uint32_t* aPos1,
nsTArray<Accessible*>* aParents2,
uint32_t* aPos2) const {
if (aAcc1 == aAcc2) {
return aAcc1;
}
MOZ_ASSERT(aParents1->Length() == 0 || aParents2->Length() == 0,
"Wrong arguments");
// Build the chain of parents.
Accessible* p1 = aAcc1;
Accessible* p2 = aAcc2;
do {
aParents1->AppendElement(p1);
p1 = p1->Parent();
} while (p1);
do {
aParents2->AppendElement(p2);
p2 = p2->Parent();
} while (p2);
// Find where the parent chain differs
*aPos1 = aParents1->Length();
*aPos2 = aParents2->Length();
Accessible* parent = nullptr;
uint32_t len = 0;
for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
Accessible* child1 = aParents1->ElementAt(--(*aPos1));
Accessible* child2 = aParents2->ElementAt(--(*aPos2));
if (child1 != child2) break;
parent = child1;
}
return parent;
}
} // namespace a11y
} // namespace mozilla
|