2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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/. */
8 #include "DocAccessibleParent.h"
9 #include "AccAttributes.h"
10 #include "nsCocoaUtils.h"
12 #include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
14 #import "GeckoTextMarker.h"
18 CFTypeID AXTextMarkerGetTypeID();
20 AXTextMarkerRef AXTextMarkerCreate(CFAllocatorRef allocator, const UInt8* bytes,
23 const UInt8* AXTextMarkerGetBytePtr(AXTextMarkerRef text_marker);
25 size_t AXTextMarkerGetLength(AXTextMarkerRef text_marker);
27 CFTypeID AXTextMarkerRangeGetTypeID();
29 AXTextMarkerRangeRef AXTextMarkerRangeCreate(CFAllocatorRef allocator,
30 AXTextMarkerRef start_marker,
31 AXTextMarkerRef end_marker);
33 AXTextMarkerRef AXTextMarkerRangeCopyStartMarker(
34 AXTextMarkerRangeRef text_marker_range);
36 AXTextMarkerRef AXTextMarkerRangeCopyEndMarker(
37 AXTextMarkerRangeRef text_marker_range);
43 struct OpaqueGeckoTextMarker {
44 OpaqueGeckoTextMarker(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
45 : mDoc(aDoc), mID(aID), mOffset(aOffset) {}
46 OpaqueGeckoTextMarker() {}
52 static bool DocumentExists(Accessible* aDoc, uintptr_t aDocPtr) {
53 if (reinterpret_cast<uintptr_t>(aDoc) == aDocPtr) {
57 if (aDoc->IsLocal()) {
58 DocAccessible* docAcc = aDoc->AsLocal()->AsDoc();
59 uint32_t docCount = docAcc->ChildDocumentCount();
60 for (uint32_t i = 0; i < docCount; i++) {
61 if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) {
66 DocAccessibleParent* docProxy = aDoc->AsRemote()->AsDoc();
67 size_t docCount = docProxy->ChildDocCount();
68 for (uint32_t i = 0; i < docCount; i++) {
69 if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) {
80 GeckoTextMarker::GeckoTextMarker(Accessible* aDoc,
81 AXTextMarkerRef aTextMarker) {
83 OpaqueGeckoTextMarker opaqueMarker;
85 AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
86 memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker),
87 sizeof(OpaqueGeckoTextMarker));
88 if (DocumentExists(aDoc, opaqueMarker.mDoc)) {
89 Accessible* doc = reinterpret_cast<Accessible*>(opaqueMarker.mDoc);
90 if (doc->IsRemote()) {
91 mContainer = doc->AsRemote()->AsDoc()->GetAccessible(opaqueMarker.mID);
93 mContainer = doc->AsLocal()->AsDoc()->GetAccessibleByUniqueID(
94 reinterpret_cast<void*>(opaqueMarker.mID));
98 mOffset = opaqueMarker.mOffset;
100 mContainer = nullptr;
105 GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
107 if (aRoot->IsRemote()) {
109 uint64_t containerID = 0;
110 DocAccessibleParent* ipcDoc = aRoot->AsRemote()->Document();
111 Unused << ipcDoc->GetPlatformExtension()->SendOffsetAtIndex(
112 aRoot->AsRemote()->ID(), aIndex, &containerID, &offset);
113 RemoteAccessible* container = ipcDoc->GetAccessible(containerID);
114 return GeckoTextMarker(container, offset);
115 } else if (auto htWrap = static_cast<HyperTextAccessibleWrap*>(
116 aRoot->AsLocal()->AsHyperText())) {
118 HyperTextAccessible* container = nullptr;
119 htWrap->OffsetAtIndex(aIndex, &container, &offset);
120 return GeckoTextMarker(container, offset);
123 return GeckoTextMarker();
126 id GeckoTextMarker::CreateAXTextMarker() {
132 if (mContainer->IsRemote()) {
133 doc = mContainer->AsRemote()->Document();
135 doc = mContainer->AsLocal()->Document();
138 uintptr_t identifier =
139 mContainer->IsRemote()
140 ? mContainer->AsRemote()->ID()
141 : reinterpret_cast<uintptr_t>(mContainer->AsLocal()->UniqueID());
143 OpaqueGeckoTextMarker opaqueMarker(reinterpret_cast<uintptr_t>(doc),
144 identifier, mOffset);
145 AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
146 kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&opaqueMarker),
147 sizeof(OpaqueGeckoTextMarker));
149 return [static_cast<id>(cf_text_marker) autorelease];
152 bool GeckoTextMarker::operator<(const GeckoTextMarker& aPoint) const {
153 if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
155 // Build the chain of parents
156 AutoTArray<Accessible*, 30> parents1, parents2;
157 Accessible* p1 = mContainer;
159 parents1.AppendElement(p1);
163 Accessible* p2 = aPoint.mContainer;
165 parents2.AppendElement(p2);
169 // An empty chain of parents means one of the containers was null.
170 MOZ_ASSERT(parents1.Length() != 0 && parents2.Length() != 0,
171 "have empty chain of parents!");
173 // Find where the parent chain differs
174 uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
175 for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
176 Accessible* child1 = parents1.ElementAt(--pos1);
177 Accessible* child2 = parents2.ElementAt(--pos2);
178 if (child1 != child2) {
179 return child1->IndexInParent() < child2->IndexInParent();
184 // If parents1 is a superset of parents2 then mContainer is a
185 // descendant of aPoint.mContainer. The next element down in parents1
186 // is mContainer's ancestor that is the child of aPoint.mContainer.
187 // We compare its end offset in aPoint.mContainer with aPoint.mOffset.
188 Accessible* child = parents1.ElementAt(pos1 - 1);
189 MOZ_ASSERT(child->Parent() == aPoint.mContainer);
191 uint32_t endOffset = child->IsRemote()
192 ? child->AsRemote()->EndOffset(&unused)
193 : child->AsLocal()->EndOffset();
194 return endOffset < static_cast<uint32_t>(aPoint.mOffset);
198 // If parents2 is a superset of parents1 then aPoint.mContainer is a
199 // descendant of mContainer. The next element down in parents2
200 // is aPoint.mContainer's ancestor that is the child of mContainer.
201 // We compare its start offset in mContainer with mOffset.
202 Accessible* child = parents2.ElementAt(pos2 - 1);
203 MOZ_ASSERT(child->Parent() == mContainer);
205 uint32_t startOffset = child->IsRemote()
206 ? child->AsRemote()->StartOffset(&unused)
207 : child->AsLocal()->StartOffset();
208 return static_cast<uint32_t>(mOffset) <= startOffset;
211 MOZ_ASSERT_UNREACHABLE("Broken tree?!");
215 bool GeckoTextMarker::IsEditableRoot() {
216 uint64_t state = mContainer->IsRemote() ? mContainer->AsRemote()->State()
217 : mContainer->AsLocal()->State();
218 if ((state & states::EDITABLE) == 0) {
222 Accessible* parent = mContainer->Parent();
224 // Not sure when this can happen, but it would technically be an editable
229 state = parent->IsRemote() ? parent->AsRemote()->State()
230 : parent->AsLocal()->State();
232 return (state & states::EDITABLE) == 0;
235 bool GeckoTextMarker::Next() {
236 if (mContainer->IsRemote()) {
237 int32_t nextOffset = 0;
238 uint64_t nextContainerID = 0;
239 DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
240 Unused << ipcDoc->GetPlatformExtension()->SendNextClusterAt(
241 mContainer->AsRemote()->ID(), mOffset, &nextContainerID, &nextOffset);
242 RemoteAccessible* nextContainer = ipcDoc->GetAccessible(nextContainerID);
244 nextContainer != mContainer->AsRemote() || nextOffset != mOffset;
245 mContainer = nextContainer;
246 mOffset = nextOffset;
248 } else if (auto htWrap = ContainerAsHyperTextWrap()) {
249 HyperTextAccessible* nextContainer = nullptr;
250 int32_t nextOffset = 0;
251 htWrap->NextClusterAt(mOffset, &nextContainer, &nextOffset);
252 bool moved = nextContainer != htWrap || nextOffset != mOffset;
253 mContainer = nextContainer;
254 mOffset = nextOffset;
261 bool GeckoTextMarker::Previous() {
262 if (mContainer->IsRemote()) {
263 int32_t prevOffset = 0;
264 uint64_t prevContainerID = 0;
265 DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
266 Unused << ipcDoc->GetPlatformExtension()->SendPreviousClusterAt(
267 mContainer->AsRemote()->ID(), mOffset, &prevContainerID, &prevOffset);
268 RemoteAccessible* prevContainer = ipcDoc->GetAccessible(prevContainerID);
270 prevContainer != mContainer->AsRemote() || prevOffset != mOffset;
271 mContainer = prevContainer;
272 mOffset = prevOffset;
274 } else if (auto htWrap = ContainerAsHyperTextWrap()) {
275 HyperTextAccessible* prevContainer = nullptr;
276 int32_t prevOffset = 0;
277 htWrap->PreviousClusterAt(mOffset, &prevContainer, &prevOffset);
278 bool moved = prevContainer != htWrap || prevOffset != mOffset;
279 mContainer = prevContainer;
280 mOffset = prevOffset;
287 static uint32_t CharacterCount(Accessible* aContainer) {
288 if (aContainer->IsRemote()) {
289 return aContainer->AsRemote()->CharacterCount();
292 if (aContainer->AsLocal()->IsHyperText()) {
293 return aContainer->AsLocal()->AsHyperText()->CharacterCount();
299 GeckoTextMarkerRange GeckoTextMarker::Range(EWhichRange aRangeType) {
300 MOZ_ASSERT(mContainer);
301 if (mContainer->IsRemote()) {
302 int32_t startOffset = 0, endOffset = 0;
303 uint64_t startContainerID = 0, endContainerID = 0;
304 DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
305 bool success = ipcDoc->GetPlatformExtension()->SendRangeAt(
306 mContainer->AsRemote()->ID(), mOffset, aRangeType, &startContainerID,
307 &startOffset, &endContainerID, &endOffset);
309 return GeckoTextMarkerRange(
310 GeckoTextMarker(ipcDoc->GetAccessible(startContainerID), startOffset),
311 GeckoTextMarker(ipcDoc->GetAccessible(endContainerID), endOffset));
313 } else if (auto htWrap = ContainerAsHyperTextWrap()) {
314 int32_t startOffset = 0, endOffset = 0;
315 HyperTextAccessible* startContainer = nullptr;
316 HyperTextAccessible* endContainer = nullptr;
317 htWrap->RangeAt(mOffset, aRangeType, &startContainer, &startOffset,
318 &endContainer, &endOffset);
319 return GeckoTextMarkerRange(GeckoTextMarker(startContainer, startOffset),
320 GeckoTextMarker(endContainer, endOffset));
323 return GeckoTextMarkerRange(GeckoTextMarker(), GeckoTextMarker());
326 Accessible* GeckoTextMarker::Leaf() {
327 MOZ_ASSERT(mContainer);
328 if (mContainer->IsRemote()) {
330 DocAccessibleParent* ipcDoc = mContainer->AsRemote()->Document();
331 Unused << ipcDoc->GetPlatformExtension()->SendLeafAtOffset(
332 mContainer->AsRemote()->ID(), mOffset, &leafID);
333 return ipcDoc->GetAccessible(leafID);
334 } else if (auto htWrap = ContainerAsHyperTextWrap()) {
335 return htWrap->LeafAtOffset(mOffset);
341 // GeckoTextMarkerRange
343 GeckoTextMarkerRange::GeckoTextMarkerRange(
344 Accessible* aDoc, AXTextMarkerRangeRef aTextMarkerRange) {
345 if (!aTextMarkerRange ||
346 CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
350 AXTextMarkerRef start_marker(
351 AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
352 AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));
354 mStart = GeckoTextMarker(aDoc, start_marker);
355 mEnd = GeckoTextMarker(aDoc, end_marker);
357 CFRelease(start_marker);
358 CFRelease(end_marker);
361 GeckoTextMarkerRange::GeckoTextMarkerRange(Accessible* aAccessible) {
362 if (aAccessible->IsHyperText()) {
363 // The accessible is a hypertext. Initialize range to its inner text range.
364 mStart = GeckoTextMarker(aAccessible, 0);
365 mEnd = GeckoTextMarker(aAccessible, (CharacterCount(aAccessible)));
367 // The accessible is not a hypertext (maybe a text leaf?). Initialize range
368 // to its offsets in its container.
369 mStart = GeckoTextMarker(aAccessible->Parent(), 0);
370 mEnd = GeckoTextMarker(aAccessible->Parent(), 0);
371 if (mStart.mContainer->IsRemote()) {
372 DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
373 Unused << ipcDoc->GetPlatformExtension()->SendRangeOfChild(
374 mStart.mContainer->AsRemote()->ID(), aAccessible->AsRemote()->ID(),
375 &mStart.mOffset, &mEnd.mOffset);
376 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
377 htWrap->RangeOfChild(aAccessible->AsLocal(), &mStart.mOffset,
383 id GeckoTextMarkerRange::CreateAXTextMarkerRange() {
388 AXTextMarkerRangeRef cf_text_marker_range =
389 AXTextMarkerRangeCreate(kCFAllocatorDefault, mStart.CreateAXTextMarker(),
390 mEnd.CreateAXTextMarker());
391 return [static_cast<id>(cf_text_marker_range) autorelease];
394 NSString* GeckoTextMarkerRange::Text() const {
396 if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
397 DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
398 Unused << ipcDoc->GetPlatformExtension()->SendTextForRange(
399 mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
400 mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &text);
401 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
402 htWrap->TextForRange(text, mStart.mOffset, mEnd.ContainerAsHyperTextWrap(),
405 return nsCocoaUtils::ToNSString(text);
408 static NSColor* ColorFromColor(const Color& aColor) {
409 return [NSColor colorWithCalibratedRed:NS_GET_R(aColor.mValue) / 255.0
410 green:NS_GET_G(aColor.mValue) / 255.0
411 blue:NS_GET_B(aColor.mValue) / 255.0
415 static NSDictionary* StringAttributesFromAttributes(AccAttributes* aAttributes,
416 Accessible* aContainer) {
417 NSMutableDictionary* attrDict =
418 [NSMutableDictionary dictionaryWithCapacity:aAttributes->Count()];
419 NSMutableDictionary* fontAttrDict = [[NSMutableDictionary alloc] init];
420 [attrDict setObject:fontAttrDict forKey:@"AXFont"];
421 for (auto iter : *aAttributes) {
422 if (iter.Name() == nsGkAtoms::backgroundColor) {
423 if (Maybe<Color> value = iter.Value<Color>()) {
424 NSColor* color = ColorFromColor(*value);
425 [attrDict setObject:(__bridge id)color.CGColor
426 forKey:@"AXBackgroundColor"];
428 } else if (iter.Name() == nsGkAtoms::color) {
429 if (Maybe<Color> value = iter.Value<Color>()) {
430 NSColor* color = ColorFromColor(*value);
431 [attrDict setObject:(__bridge id)color.CGColor
432 forKey:@"AXForegroundColor"];
434 } else if (iter.Name() == nsGkAtoms::font_size) {
435 if (Maybe<FontSize> pointSize = iter.Value<FontSize>()) {
436 int32_t fontPixelSize = static_cast<int32_t>(pointSize->mValue * 4 / 3);
437 [fontAttrDict setObject:@(fontPixelSize) forKey:@"AXFontSize"];
439 } else if (iter.Name() == nsGkAtoms::font_family) {
440 nsAutoString fontFamily;
441 iter.ValueAsString(fontFamily);
442 [fontAttrDict setObject:nsCocoaUtils::ToNSString(fontFamily)
443 forKey:@"AXFontFamily"];
444 } else if (iter.Name() == nsGkAtoms::textUnderlineColor) {
445 [attrDict setObject:@1 forKey:@"AXUnderline"];
446 if (Maybe<Color> value = iter.Value<Color>()) {
447 NSColor* color = ColorFromColor(*value);
448 [attrDict setObject:(__bridge id)color.CGColor
449 forKey:@"AXUnderlineColor"];
451 } else if (iter.Name() == nsGkAtoms::invalid) {
452 // XXX: There is currently no attribute for grammar
453 if (auto value = iter.Value<RefPtr<nsAtom>>()) {
454 if (*value == nsGkAtoms::spelling) {
455 [attrDict setObject:@YES
456 forKey:NSAccessibilityMarkedMisspelledTextAttribute];
460 nsAutoString valueStr;
461 iter.ValueAsString(valueStr);
463 iter.NameAsString(keyStr);
464 [attrDict setObject:nsCocoaUtils::ToNSString(valueStr)
465 forKey:nsCocoaUtils::ToNSString(keyStr)];
469 mozAccessible* container = GetNativeFromGeckoAccessible(aContainer);
470 id<MOXAccessible> link =
471 [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
472 return [[moxAcc moxRole] isEqualToString:NSAccessibilityLinkRole];
475 [attrDict setObject:link forKey:@"AXLink"];
478 id<MOXAccessible> heading =
479 [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
480 return [[moxAcc moxRole] isEqualToString:@"AXHeading"];
483 [attrDict setObject:[heading moxValue] forKey:@"AXHeadingLevel"];
489 NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
490 NSMutableAttributedString* str =
491 [[[NSMutableAttributedString alloc] init] autorelease];
493 if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
494 nsTArray<TextAttributesRun> textAttributesRuns;
495 DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
496 Unused << ipcDoc->GetPlatformExtension()->SendAttributedTextForRange(
497 mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
498 mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &textAttributesRuns);
500 for (size_t i = 0; i < textAttributesRuns.Length(); i++) {
501 AccAttributes* attributes =
502 textAttributesRuns.ElementAt(i).TextAttributes();
503 RemoteAccessible* container =
504 ipcDoc->GetAccessible(textAttributesRuns.ElementAt(i).ContainerID());
506 NSAttributedString* substr = [[[NSAttributedString alloc]
507 initWithString:nsCocoaUtils::ToNSString(
508 textAttributesRuns.ElementAt(i).Text())
509 attributes:StringAttributesFromAttributes(attributes, container)]
512 [str appendAttributedString:substr];
514 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
515 nsTArray<nsString> texts;
516 nsTArray<LocalAccessible*> containers;
517 nsTArray<RefPtr<AccAttributes>> props;
519 htWrap->AttributedTextForRange(texts, props, containers, mStart.mOffset,
520 mEnd.ContainerAsHyperTextWrap(),
523 MOZ_ASSERT(texts.Length() == props.Length() &&
524 texts.Length() == containers.Length());
526 for (size_t i = 0; i < texts.Length(); i++) {
527 NSAttributedString* substr = [[[NSAttributedString alloc]
528 initWithString:nsCocoaUtils::ToNSString(texts.ElementAt(i))
529 attributes:StringAttributesFromAttributes(
530 props.ElementAt(i), containers.ElementAt(i))]
532 [str appendAttributedString:substr];
539 int32_t GeckoTextMarkerRange::Length() const {
541 if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
542 DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
543 Unused << ipcDoc->GetPlatformExtension()->SendLengthForRange(
544 mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
545 mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &length);
546 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
547 length = htWrap->LengthForRange(
548 mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
554 NSValue* GeckoTextMarkerRange::Bounds() const {
556 if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
557 DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
558 Unused << ipcDoc->GetPlatformExtension()->SendBoundsForRange(
559 mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
560 mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset, &rect);
561 } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
562 rect = htWrap->BoundsForRange(
563 mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
566 NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
567 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
569 NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor,
570 [mainView frame].size.height -
571 static_cast<CGFloat>(rect.y + rect.height) / scaleFactor,
572 static_cast<CGFloat>(rect.width) / scaleFactor,
573 static_cast<CGFloat>(rect.height) / scaleFactor);
575 return [NSValue valueWithRect:r];
578 void GeckoTextMarkerRange::Select() const {
579 if (mStart.mContainer->IsRemote() && mEnd.mContainer->IsRemote()) {
580 DocAccessibleParent* ipcDoc = mStart.mContainer->AsRemote()->Document();
581 Unused << ipcDoc->GetPlatformExtension()->SendSelectRange(
582 mStart.mContainer->AsRemote()->ID(), mStart.mOffset,
583 mEnd.mContainer->AsRemote()->ID(), mEnd.mOffset);
584 } else if (RefPtr<HyperTextAccessibleWrap> htWrap =
585 mStart.ContainerAsHyperTextWrap()) {
586 RefPtr<HyperTextAccessibleWrap> end = mEnd.ContainerAsHyperTextWrap();
587 htWrap->SelectRange(mStart.mOffset, end, mEnd.mOffset);
591 bool GeckoTextMarkerRange::Crop(Accessible* aContainer) {
592 GeckoTextMarker containerStart(aContainer, 0);
593 GeckoTextMarker containerEnd(aContainer, CharacterCount(aContainer));
595 if (mEnd < containerStart || containerEnd < mStart) {
596 // The range ends before the container, or starts after it.
600 if (mStart < containerStart) {
601 // If range start is before container start, adjust range start to
602 // start of container.
603 mStart = containerStart;
606 if (containerEnd < mEnd) {
607 // If range end is after container end, adjust range end to end of