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