1/* clang-format off */ 2/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 3/* clang-format on */ 4/* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8#include "DocAccessibleParent.h" 9#include "AccAttributes.h" 10#include "AccessibleOrProxy.h" 11#include "nsCocoaUtils.h" 12 13#include "mozilla/a11y/DocAccessiblePlatformExtParent.h" 14 15#import "GeckoTextMarker.h" 16 17extern "C" { 18 19CFTypeID AXTextMarkerGetTypeID(); 20 21AXTextMarkerRef AXTextMarkerCreate(CFAllocatorRef allocator, const UInt8* bytes, 22 CFIndex length); 23 24const UInt8* AXTextMarkerGetBytePtr(AXTextMarkerRef text_marker); 25 26size_t AXTextMarkerGetLength(AXTextMarkerRef text_marker); 27 28CFTypeID AXTextMarkerRangeGetTypeID(); 29 30AXTextMarkerRangeRef AXTextMarkerRangeCreate(CFAllocatorRef allocator, 31 AXTextMarkerRef start_marker, 32 AXTextMarkerRef end_marker); 33 34AXTextMarkerRef AXTextMarkerRangeCopyStartMarker( 35 AXTextMarkerRangeRef text_marker_range); 36 37AXTextMarkerRef AXTextMarkerRangeCopyEndMarker( 38 AXTextMarkerRangeRef text_marker_range); 39} 40 41namespace mozilla { 42namespace a11y { 43 44struct OpaqueGeckoTextMarker { 45 OpaqueGeckoTextMarker(uintptr_t aDoc, uintptr_t aID, int32_t aOffset) 46 : mDoc(aDoc), mID(aID), mOffset(aOffset) {} 47 OpaqueGeckoTextMarker() {} 48 uintptr_t mDoc; 49 uintptr_t mID; 50 int32_t mOffset; 51}; 52 53static bool DocumentExists(AccessibleOrProxy aDoc, uintptr_t aDocPtr) { 54 if (aDoc.Bits() == aDocPtr) { 55 return true; 56 } 57 58 if (aDoc.IsAccessible()) { 59 DocAccessible* docAcc = aDoc.AsAccessible()->AsDoc(); 60 uint32_t docCount = docAcc->ChildDocumentCount(); 61 for (uint32_t i = 0; i < docCount; i++) { 62 if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) { 63 return true; 64 } 65 } 66 } else { 67 DocAccessibleParent* docProxy = aDoc.AsProxy()->AsDoc(); 68 size_t docCount = docProxy->ChildDocCount(); 69 for (uint32_t i = 0; i < docCount; i++) { 70 if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) { 71 return true; 72 } 73 } 74 } 75 76 return false; 77} 78 79// GeckoTextMarker 80 81GeckoTextMarker::GeckoTextMarker(AccessibleOrProxy aDoc, 82 AXTextMarkerRef aTextMarker) { 83 MOZ_ASSERT(!aDoc.IsNull()); 84 OpaqueGeckoTextMarker opaqueMarker; 85 if (aTextMarker && 86 AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) { 87 memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker), 88 sizeof(OpaqueGeckoTextMarker)); 89 if (DocumentExists(aDoc, opaqueMarker.mDoc)) { 90 AccessibleOrProxy doc; 91 doc.SetBits(opaqueMarker.mDoc); 92 if (doc.IsProxy()) { 93 mContainer = doc.AsProxy()->AsDoc()->GetAccessible(opaqueMarker.mID); 94 } else { 95 mContainer = doc.AsAccessible()->AsDoc()->GetAccessibleByUniqueID( 96 reinterpret_cast<void*>(opaqueMarker.mID)); 97 } 98 } 99 100 mOffset = opaqueMarker.mOffset; 101 } 102} 103 104GeckoTextMarker GeckoTextMarker::MarkerFromIndex(const AccessibleOrProxy& aRoot, 105 int32_t aIndex) { 106 if (aRoot.IsProxy()) { 107 int32_t offset = 0; 108 uint64_t containerID = 0; 109 DocAccessibleParent* ipcDoc = aRoot.AsProxy()->Document(); 110 Unused << ipcDoc->GetPlatformExtension()->SendOffsetAtIndex( 111 aRoot.AsProxy()->ID(), aIndex, &containerID, &offset); 112 RemoteAccessible* container = ipcDoc->GetAccessible(containerID); 113 return GeckoTextMarker(container, offset); 114 } else if (auto htWrap = static_cast<HyperTextAccessibleWrap*>( 115 aRoot.AsAccessible()->AsHyperText())) { 116 int32_t offset = 0; 117 HyperTextAccessible* container = nullptr; 118 htWrap->OffsetAtIndex(aIndex, &container, &offset); 119 return GeckoTextMarker(container, offset); 120 } 121 122 return GeckoTextMarker(); 123} 124 125id GeckoTextMarker::CreateAXTextMarker() { 126 if (!IsValid()) { 127 return nil; 128 } 129 130 AccessibleOrProxy doc; 131 if (mContainer.IsProxy()) { 132 doc = mContainer.AsProxy()->Document(); 133 } else { 134 doc = mContainer.AsAccessible()->Document(); 135 } 136 137 uintptr_t identifier = 138 mContainer.IsProxy() 139 ? mContainer.AsProxy()->ID() 140 : reinterpret_cast<uintptr_t>(mContainer.AsAccessible()->UniqueID()); 141 142 OpaqueGeckoTextMarker opaqueMarker(doc.Bits(), identifier, mOffset); 143 AXTextMarkerRef cf_text_marker = AXTextMarkerCreate( 144 kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&opaqueMarker), 145 sizeof(OpaqueGeckoTextMarker)); 146 147 return [static_cast<id>(cf_text_marker) autorelease]; 148} 149 150bool GeckoTextMarker::operator<(const GeckoTextMarker& aPoint) const { 151 if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset; 152 153 // Build the chain of parents 154 AutoTArray<AccessibleOrProxy, 30> parents1, parents2; 155 AccessibleOrProxy p1 = mContainer; 156 while (!p1.IsNull()) { 157 parents1.AppendElement(p1); 158 p1 = p1.Parent(); 159 } 160 161 AccessibleOrProxy p2 = aPoint.mContainer; 162 while (!p2.IsNull()) { 163 parents2.AppendElement(p2); 164 p2 = p2.Parent(); 165 } 166 167 // An empty chain of parents means one of the containers was null. 168 MOZ_ASSERT(parents1.Length() != 0 && parents2.Length() != 0, 169 "have empty chain of parents!"); 170 171 // Find where the parent chain differs 172 uint32_t pos1 = parents1.Length(), pos2 = parents2.Length(); 173 for (uint32_t len = std::min(pos1, pos2); len > 0; --len) { 174 AccessibleOrProxy child1 = parents1.ElementAt(--pos1); 175 AccessibleOrProxy child2 = parents2.ElementAt(--pos2); 176 if (child1 != child2) { 177 return child1.IndexInParent() < child2.IndexInParent(); 178 } 179 } 180 181 if (pos1 != 0) { 182 // If parents1 is a superset of parents2 then mContainer is a 183 // descendant of aPoint.mContainer. The next element down in parents1 184 // is mContainer's ancestor that is the child of aPoint.mContainer. 185 // We compare its end offset in aPoint.mContainer with aPoint.mOffset. 186 AccessibleOrProxy child = parents1.ElementAt(pos1 - 1); 187 MOZ_ASSERT(child.Parent() == aPoint.mContainer); 188 bool unused; 189 uint32_t endOffset = child.IsProxy() ? child.AsProxy()->EndOffset(&unused) 190 : child.AsAccessible()->EndOffset(); 191 return endOffset < static_cast<uint32_t>(aPoint.mOffset); 192 } 193 194 if (pos2 != 0) { 195 // If parents2 is a superset of parents1 then aPoint.mContainer is a 196 // descendant of mContainer. The next element down in parents2 197 // is aPoint.mContainer's ancestor that is the child of mContainer. 198 // We compare its start offset in mContainer with mOffset. 199 AccessibleOrProxy child = parents2.ElementAt(pos2 - 1); 200 MOZ_ASSERT(child.Parent() == mContainer); 201 bool unused; 202 uint32_t startOffset = child.IsProxy() 203 ? child.AsProxy()->StartOffset(&unused) 204 : child.AsAccessible()->StartOffset(); 205 return static_cast<uint32_t>(mOffset) <= startOffset; 206 } 207 208 MOZ_ASSERT_UNREACHABLE("Broken tree?!"); 209 return false; 210} 211 212bool GeckoTextMarker::IsEditableRoot() { 213 uint64_t state = mContainer.IsProxy() ? mContainer.AsProxy()->State() 214 : mContainer.AsAccessible()->State(); 215 if ((state & states::EDITABLE) == 0) { 216 return false; 217 } 218 219 AccessibleOrProxy parent = mContainer.Parent(); 220 if (parent.IsNull()) { 221 // Not sure when this can happen, but it would technically be an editable 222 // root. 223 return true; 224 } 225 226 state = parent.IsProxy() ? parent.AsProxy()->State() 227 : parent.AsAccessible()->State(); 228 229 return (state & states::EDITABLE) == 0; 230} 231 232bool GeckoTextMarker::Next() { 233 if (mContainer.IsProxy()) { 234 int32_t nextOffset = 0; 235 uint64_t nextContainerID = 0; 236 DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document(); 237 Unused << ipcDoc->GetPlatformExtension()->SendNextClusterAt( 238 mContainer.AsProxy()->ID(), mOffset, &nextContainerID, &nextOffset); 239 RemoteAccessible* nextContainer = ipcDoc->GetAccessible(nextContainerID); 240 bool moved = nextContainer != mContainer.AsProxy() || nextOffset != mOffset; 241 mContainer = nextContainer; 242 mOffset = nextOffset; 243 return moved; 244 } else if (auto htWrap = ContainerAsHyperTextWrap()) { 245 HyperTextAccessible* nextContainer = nullptr; 246 int32_t nextOffset = 0; 247 htWrap->NextClusterAt(mOffset, &nextContainer, &nextOffset); 248 bool moved = nextContainer != htWrap || nextOffset != mOffset; 249 mContainer = nextContainer; 250 mOffset = nextOffset; 251 return moved; 252 } 253 254 return false; 255} 256 257bool GeckoTextMarker::Previous() { 258 if (mContainer.IsProxy()) { 259 int32_t prevOffset = 0; 260 uint64_t prevContainerID = 0; 261 DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document(); 262 Unused << ipcDoc->GetPlatformExtension()->SendPreviousClusterAt( 263 mContainer.AsProxy()->ID(), mOffset, &prevContainerID, &prevOffset); 264 RemoteAccessible* prevContainer = ipcDoc->GetAccessible(prevContainerID); 265 bool moved = prevContainer != mContainer.AsProxy() || prevOffset != mOffset; 266 mContainer = prevContainer; 267 mOffset = prevOffset; 268 return moved; 269 } else if (auto htWrap = ContainerAsHyperTextWrap()) { 270 HyperTextAccessible* prevContainer = nullptr; 271 int32_t prevOffset = 0; 272 htWrap->PreviousClusterAt(mOffset, &prevContainer, &prevOffset); 273 bool moved = prevContainer != htWrap || prevOffset != mOffset; 274 mContainer = prevContainer; 275 mOffset = prevOffset; 276 return moved; 277 } 278 279 return false; 280} 281 282static uint32_t CharacterCount(const AccessibleOrProxy& aContainer) { 283 if (aContainer.IsProxy()) { 284 return aContainer.AsProxy()->CharacterCount(); 285 } 286 287 if (aContainer.AsAccessible()->IsHyperText()) { 288 return aContainer.AsAccessible()->AsHyperText()->CharacterCount(); 289 } 290 291 return 0; 292} 293 294GeckoTextMarkerRange GeckoTextMarker::Range(EWhichRange aRangeType) { 295 MOZ_ASSERT(!mContainer.IsNull()); 296 if (mContainer.IsProxy()) { 297 int32_t startOffset = 0, endOffset = 0; 298 uint64_t startContainerID = 0, endContainerID = 0; 299 DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document(); 300 bool success = ipcDoc->GetPlatformExtension()->SendRangeAt( 301 mContainer.AsProxy()->ID(), mOffset, aRangeType, &startContainerID, 302 &startOffset, &endContainerID, &endOffset); 303 if (success) { 304 return GeckoTextMarkerRange( 305 GeckoTextMarker(ipcDoc->GetAccessible(startContainerID), startOffset), 306 GeckoTextMarker(ipcDoc->GetAccessible(endContainerID), endOffset)); 307 } 308 } else if (auto htWrap = ContainerAsHyperTextWrap()) { 309 int32_t startOffset = 0, endOffset = 0; 310 HyperTextAccessible* startContainer = nullptr; 311 HyperTextAccessible* endContainer = nullptr; 312 htWrap->RangeAt(mOffset, aRangeType, &startContainer, &startOffset, 313 &endContainer, &endOffset); 314 return GeckoTextMarkerRange(GeckoTextMarker(startContainer, startOffset), 315 GeckoTextMarker(endContainer, endOffset)); 316 } 317 318 return GeckoTextMarkerRange(GeckoTextMarker(), GeckoTextMarker()); 319} 320 321AccessibleOrProxy GeckoTextMarker::Leaf() { 322 MOZ_ASSERT(!mContainer.IsNull()); 323 if (mContainer.IsProxy()) { 324 uint64_t leafID = 0; 325 DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document(); 326 Unused << ipcDoc->GetPlatformExtension()->SendLeafAtOffset( 327 mContainer.AsProxy()->ID(), mOffset, &leafID); 328 return ipcDoc->GetAccessible(leafID); 329 } else if (auto htWrap = ContainerAsHyperTextWrap()) { 330 return htWrap->LeafAtOffset(mOffset); 331 } 332 333 return mContainer; 334} 335 336// GeckoTextMarkerRange 337 338GeckoTextMarkerRange::GeckoTextMarkerRange( 339 AccessibleOrProxy aDoc, AXTextMarkerRangeRef aTextMarkerRange) { 340 if (!aTextMarkerRange || 341 CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) { 342 return; 343 } 344 345 AXTextMarkerRef start_marker( 346 AXTextMarkerRangeCopyStartMarker(aTextMarkerRange)); 347 AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange)); 348 349 mStart = GeckoTextMarker(aDoc, start_marker); 350 mEnd = GeckoTextMarker(aDoc, end_marker); 351 352 CFRelease(start_marker); 353 CFRelease(end_marker); 354} 355 356GeckoTextMarkerRange::GeckoTextMarkerRange( 357 const AccessibleOrProxy& aAccessible) { 358 if ((aAccessible.IsAccessible() && 359 aAccessible.AsAccessible()->IsHyperText()) || 360 (aAccessible.IsProxy() && aAccessible.AsProxy()->IsHyperText())) { 361 // The accessible is a hypertext. Initialize range to its inner text range. 362 mStart = GeckoTextMarker(aAccessible, 0); 363 mEnd = GeckoTextMarker(aAccessible, (CharacterCount(aAccessible))); 364 } else { 365 // The accessible is not a hypertext (maybe a text leaf?). Initialize range 366 // to its offsets in its container. 367 mStart = GeckoTextMarker(aAccessible.Parent(), 0); 368 mEnd = GeckoTextMarker(aAccessible.Parent(), 0); 369 if (mStart.mContainer.IsProxy()) { 370 DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document(); 371 Unused << ipcDoc->GetPlatformExtension()->SendRangeOfChild( 372 mStart.mContainer.AsProxy()->ID(), aAccessible.AsProxy()->ID(), 373 &mStart.mOffset, &mEnd.mOffset); 374 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) { 375 htWrap->RangeOfChild(aAccessible.AsAccessible(), &mStart.mOffset, 376 &mEnd.mOffset); 377 } 378 } 379} 380 381id GeckoTextMarkerRange::CreateAXTextMarkerRange() { 382 if (!IsValid()) { 383 return nil; 384 } 385 386 AXTextMarkerRangeRef cf_text_marker_range = 387 AXTextMarkerRangeCreate(kCFAllocatorDefault, mStart.CreateAXTextMarker(), 388 mEnd.CreateAXTextMarker()); 389 return [static_cast<id>(cf_text_marker_range) autorelease]; 390} 391 392NSString* GeckoTextMarkerRange::Text() const { 393 nsAutoString text; 394 if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) { 395 DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document(); 396 Unused << ipcDoc->GetPlatformExtension()->SendTextForRange( 397 mStart.mContainer.AsProxy()->ID(), mStart.mOffset, 398 mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &text); 399 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) { 400 htWrap->TextForRange(text, mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), 401 mEnd.mOffset); 402 } 403 return nsCocoaUtils::ToNSString(text); 404} 405 406static NSColor* ColorFromColor(const Color& aColor) { 407 return [NSColor colorWithCalibratedRed:NS_GET_R(aColor.mValue) / 255.0 408 green:NS_GET_G(aColor.mValue) / 255.0 409 blue:NS_GET_B(aColor.mValue) / 255.0 410 alpha:1.0]; 411} 412 413static NSDictionary* StringAttributesFromAttributes( 414 AccAttributes* aAttributes, const AccessibleOrProxy& aContainer) { 415 NSMutableDictionary* attrDict = 416 [NSMutableDictionary dictionaryWithCapacity:aAttributes->Count()]; 417 NSMutableDictionary* fontAttrDict = [[NSMutableDictionary alloc] init]; 418 [attrDict setObject:fontAttrDict forKey:@"AXFont"]; 419 for (auto iter : *aAttributes) { 420 if (iter.Name() == nsGkAtoms::backgroundColor) { 421 if (Maybe<Color> value = iter.Value<Color>()) { 422 NSColor* color = ColorFromColor(*value); 423 [attrDict setObject:(__bridge id)color.CGColor 424 forKey:@"AXBackgroundColor"]; 425 } 426 } else if (iter.Name() == nsGkAtoms::color) { 427 if (Maybe<Color> value = iter.Value<Color>()) { 428 NSColor* color = ColorFromColor(*value); 429 [attrDict setObject:(__bridge id)color.CGColor 430 forKey:@"AXForegroundColor"]; 431 } 432 } else if (iter.Name() == nsGkAtoms::font_size) { 433 if (Maybe<FontSize> pointSize = iter.Value<FontSize>()) { 434 int32_t fontPixelSize = static_cast<int32_t>(pointSize->mValue * 4 / 3); 435 [fontAttrDict setObject:@(fontPixelSize) forKey:@"AXFontSize"]; 436 } 437 } else if (iter.Name() == nsGkAtoms::font_family) { 438 nsAutoString fontFamily; 439 iter.ValueAsString(fontFamily); 440 [fontAttrDict setObject:nsCocoaUtils::ToNSString(fontFamily) 441 forKey:@"AXFontFamily"]; 442 } else if (iter.Name() == nsGkAtoms::textUnderlineColor) { 443 [attrDict setObject:@1 forKey:@"AXUnderline"]; 444 if (Maybe<Color> value = iter.Value<Color>()) { 445 NSColor* color = ColorFromColor(*value); 446 [attrDict setObject:(__bridge id)color.CGColor 447 forKey:@"AXUnderlineColor"]; 448 } 449 } else if (iter.Name() == nsGkAtoms::invalid) { 450 // XXX: There is currently no attribute for grammar 451 if (Maybe<nsAtom*> value = iter.Value<nsAtom*>()) { 452 if (*value == nsGkAtoms::spelling) { 453 [attrDict setObject:@YES 454 forKey:NSAccessibilityMarkedMisspelledTextAttribute]; 455 } 456 } 457 } else { 458 nsAutoString valueStr; 459 iter.ValueAsString(valueStr); 460 nsAutoString keyStr; 461 iter.NameAsString(keyStr); 462 [attrDict setObject:nsCocoaUtils::ToNSString(valueStr) 463 forKey:nsCocoaUtils::ToNSString(keyStr)]; 464 } 465 } 466 467 mozAccessible* container = GetNativeFromGeckoAccessible(aContainer); 468 id<MOXAccessible> link = 469 [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) { 470 return [[moxAcc moxRole] isEqualToString:NSAccessibilityLinkRole]; 471 }]; 472 if (link) { 473 [attrDict setObject:link forKey:@"AXLink"]; 474 } 475 476 id<MOXAccessible> heading = 477 [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) { 478 return [[moxAcc moxRole] isEqualToString:@"AXHeading"]; 479 }]; 480 if (heading) { 481 [attrDict setObject:[heading moxValue] forKey:@"AXHeadingLevel"]; 482 } 483 484 return attrDict; 485} 486 487NSAttributedString* GeckoTextMarkerRange::AttributedText() const { 488 NSMutableAttributedString* str = 489 [[[NSMutableAttributedString alloc] init] autorelease]; 490 491 if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) { 492 nsTArray<TextAttributesRun> textAttributesRuns; 493 DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document(); 494 Unused << ipcDoc->GetPlatformExtension()->SendAttributedTextForRange( 495 mStart.mContainer.AsProxy()->ID(), mStart.mOffset, 496 mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &textAttributesRuns); 497 498 for (size_t i = 0; i < textAttributesRuns.Length(); i++) { 499 AccAttributes* attributes = 500 textAttributesRuns.ElementAt(i).TextAttributes(); 501 RemoteAccessible* container = 502 ipcDoc->GetAccessible(textAttributesRuns.ElementAt(i).ContainerID()); 503 504 NSAttributedString* substr = [[[NSAttributedString alloc] 505 initWithString:nsCocoaUtils::ToNSString( 506 textAttributesRuns.ElementAt(i).Text()) 507 attributes:StringAttributesFromAttributes(attributes, container)] 508 autorelease]; 509 510 [str appendAttributedString:substr]; 511 } 512 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) { 513 nsTArray<nsString> texts; 514 nsTArray<LocalAccessible*> containers; 515 nsTArray<RefPtr<AccAttributes>> props; 516 517 htWrap->AttributedTextForRange(texts, props, containers, mStart.mOffset, 518 mEnd.ContainerAsHyperTextWrap(), 519 mEnd.mOffset); 520 521 MOZ_ASSERT(texts.Length() == props.Length() && 522 texts.Length() == containers.Length()); 523 524 for (size_t i = 0; i < texts.Length(); i++) { 525 NSAttributedString* substr = [[[NSAttributedString alloc] 526 initWithString:nsCocoaUtils::ToNSString(texts.ElementAt(i)) 527 attributes:StringAttributesFromAttributes( 528 props.ElementAt(i), containers.ElementAt(i))] 529 autorelease]; 530 [str appendAttributedString:substr]; 531 } 532 } 533 534 return str; 535} 536 537int32_t GeckoTextMarkerRange::Length() const { 538 int32_t length = 0; 539 if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) { 540 DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document(); 541 Unused << ipcDoc->GetPlatformExtension()->SendLengthForRange( 542 mStart.mContainer.AsProxy()->ID(), mStart.mOffset, 543 mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &length); 544 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) { 545 length = htWrap->LengthForRange( 546 mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset); 547 } 548 549 return length; 550} 551 552NSValue* GeckoTextMarkerRange::Bounds() const { 553 nsIntRect rect; 554 if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) { 555 DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document(); 556 Unused << ipcDoc->GetPlatformExtension()->SendBoundsForRange( 557 mStart.mContainer.AsProxy()->ID(), mStart.mOffset, 558 mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &rect); 559 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) { 560 rect = htWrap->BoundsForRange( 561 mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset); 562 } 563 564 NSScreen* mainView = [[NSScreen screens] objectAtIndex:0]; 565 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView); 566 NSRect r = 567 NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor, 568 [mainView frame].size.height - 569 static_cast<CGFloat>(rect.y + rect.height) / scaleFactor, 570 static_cast<CGFloat>(rect.width) / scaleFactor, 571 static_cast<CGFloat>(rect.height) / scaleFactor); 572 573 return [NSValue valueWithRect:r]; 574} 575 576void GeckoTextMarkerRange::Select() const { 577 if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) { 578 DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document(); 579 Unused << ipcDoc->GetPlatformExtension()->SendSelectRange( 580 mStart.mContainer.AsProxy()->ID(), mStart.mOffset, 581 mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset); 582 } else if (RefPtr<HyperTextAccessibleWrap> htWrap = 583 mStart.ContainerAsHyperTextWrap()) { 584 RefPtr<HyperTextAccessibleWrap> end = mEnd.ContainerAsHyperTextWrap(); 585 htWrap->SelectRange(mStart.mOffset, end, mEnd.mOffset); 586 } 587} 588 589bool GeckoTextMarkerRange::Crop(const AccessibleOrProxy& aContainer) { 590 GeckoTextMarker containerStart(aContainer, 0); 591 GeckoTextMarker containerEnd(aContainer, CharacterCount(aContainer)); 592 593 if (mEnd < containerStart || containerEnd < mStart) { 594 // The range ends before the container, or starts after it. 595 return false; 596 } 597 598 if (mStart < containerStart) { 599 // If range start is before container start, adjust range start to 600 // start of container. 601 mStart = containerStart; 602 } 603 604 if (containerEnd < mEnd) { 605 // If range end is after container end, adjust range end to end of 606 // container. 607 mEnd = containerEnd; 608 } 609 610 return true; 611} 612} 613} 614