diff options
Diffstat (limited to '')
-rw-r--r-- | accessible/mac/HyperTextAccessibleWrap.mm | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/accessible/mac/HyperTextAccessibleWrap.mm b/accessible/mac/HyperTextAccessibleWrap.mm new file mode 100644 index 0000000000..0fff4621ea --- /dev/null +++ b/accessible/mac/HyperTextAccessibleWrap.mm @@ -0,0 +1,704 @@ +/* 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/. */ + +#include "HyperTextAccessibleWrap.h" + +#include "Accessible-inl.h" +#include "HTMLListAccessible.h" +#include "nsAccUtils.h" +#include "nsFrameSelection.h" +#include "TextRange.h" +#include "TreeWalker.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +// HyperTextIterator + +class HyperTextIterator { + public: + HyperTextIterator(HyperTextAccessible* aStartContainer, int32_t aStartOffset, + HyperTextAccessible* aEndContainer, int32_t aEndOffset) + : mCurrentContainer(aStartContainer), + mCurrentStartOffset(aStartOffset), + mCurrentEndOffset(aStartOffset), + mEndContainer(aEndContainer), + mEndOffset(aEndOffset) {} + + bool Next(); + + int32_t SegmentLength(); + + // If offset is set to a child hyperlink, adjust it so it set on the first + // offset in the deepest link. Or, if the offset to the last character, set it + // to the outermost end offset in an ancestor. Returns true if iterator was + // mutated. + bool NormalizeForward(); + + // If offset is set right after child hyperlink, adjust it so it set on the + // last offset in the deepest link. Or, if the offset is on the first + // character of a link, set it to the outermost start offset in an ancestor. + // Returns true if iterator was mutated. + bool NormalizeBackward(); + + HyperTextAccessible* mCurrentContainer; + int32_t mCurrentStartOffset; + int32_t mCurrentEndOffset; + + private: + int32_t NextLinkOffset(); + + HyperTextAccessible* mEndContainer; + int32_t mEndOffset; +}; + +bool HyperTextIterator::NormalizeForward() { + if (mCurrentStartOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT || + mCurrentStartOffset >= + static_cast<int32_t>(mCurrentContainer->CharacterCount())) { + // If this is the end of the current container, mutate to its parent's + // end offset. + if (!mCurrentContainer->IsLink()) { + // If we are not a link, it is a root hypertext accessible. + return false; + } + if (!mCurrentContainer->Parent() || + !mCurrentContainer->Parent()->IsHyperText()) { + // If we are a link, but our parent is not a hypertext accessible + // treat the current container as the root hypertext accessible. + // This can be the case with some XUL containers that are not + // hypertext accessibles. + return false; + } + uint32_t endOffset = mCurrentContainer->EndOffset(); + if (endOffset != 0) { + mCurrentContainer = mCurrentContainer->Parent()->AsHyperText(); + mCurrentStartOffset = endOffset; + + if (mCurrentContainer == mEndContainer && + mCurrentStartOffset >= mEndOffset) { + // Reached end boundary. + return false; + } + + // Call NormalizeForward recursively to get top-most link if at the end of + // one, or innermost link if at the beginning. + NormalizeForward(); + return true; + } + } else { + Accessible* link = mCurrentContainer->LinkAt( + mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset)); + + // If there is a link at this offset, mutate into it. + if (link && link->IsHyperText()) { + if (mCurrentStartOffset > 0 && + mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset) == + mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset - 1)) { + MOZ_ASSERT_UNREACHABLE("Same link for previous offset"); + return false; + } + + mCurrentContainer = link->AsHyperText(); + if (link->IsHTMLListItem()) { + Accessible* bullet = link->AsHTMLListItem()->Bullet(); + mCurrentStartOffset = bullet ? nsAccUtils::TextLength(bullet) : 0; + } else { + mCurrentStartOffset = 0; + } + + if (mCurrentContainer == mEndContainer && + mCurrentStartOffset >= mEndOffset) { + // Reached end boundary. + return false; + } + + // Call NormalizeForward recursively to get top-most embedding ancestor + // if at the end of one, or innermost link if at the beginning. + NormalizeForward(); + return true; + } + } + + return false; +} + +bool HyperTextIterator::NormalizeBackward() { + if (mCurrentStartOffset == 0) { + // If this is the start of the current container, mutate to its parent's + // start offset. + if (!mCurrentContainer->IsLink()) { + // If we are not a link, it is a root hypertext accessible. + return false; + } + if (!mCurrentContainer->Parent() || + !mCurrentContainer->Parent()->IsHyperText()) { + // If we are a link, but our parent is not a hypertext accessible + // treat the current container as the root hypertext accessible. + // This can be the case with some XUL containers that are not + // hypertext accessibles. + return false; + } + + uint32_t startOffset = mCurrentContainer->StartOffset(); + mCurrentContainer = mCurrentContainer->Parent()->AsHyperText(); + mCurrentStartOffset = startOffset; + + // Call NormalizeBackward recursively to get top-most link if at the + // beginning of one, or innermost link if at the end. + NormalizeBackward(); + return true; + } else { + Accessible* link = + mCurrentContainer->GetChildAtOffset(mCurrentStartOffset - 1); + + // If there is a link before this offset, mutate into it, + // and set the offset to its last character. + if (link && link->IsHyperText()) { + mCurrentContainer = link->AsHyperText(); + mCurrentStartOffset = mCurrentContainer->CharacterCount(); + + // Call NormalizeBackward recursively to get top-most top-most embedding + // ancestor if at the beginning of one, or innermost link if at the end. + NormalizeBackward(); + return true; + } + + if (mCurrentContainer->IsHTMLListItem() && + mCurrentContainer->AsHTMLListItem()->Bullet() == link) { + mCurrentStartOffset = 0; + NormalizeBackward(); + return true; + } + } + + return false; +} + +int32_t HyperTextIterator::SegmentLength() { + int32_t endOffset = mCurrentEndOffset < 0 + ? mCurrentContainer->CharacterCount() + : mCurrentEndOffset; + + return endOffset - mCurrentStartOffset; +} + +int32_t HyperTextIterator::NextLinkOffset() { + int32_t linkCount = mCurrentContainer->LinkCount(); + for (int32_t i = 0; i < linkCount; i++) { + Accessible* link = mCurrentContainer->LinkAt(i); + MOZ_ASSERT(link); + int32_t linkStartOffset = link->StartOffset(); + if (mCurrentStartOffset < linkStartOffset) { + return linkStartOffset; + } + } + + return -1; +} + +bool HyperTextIterator::Next() { + if (!mCurrentContainer->Document()->HasLoadState( + DocAccessible::eTreeConstructed)) { + // If the accessible tree is still being constructed the text tree + // is not in a traversable state yet. + return false; + } + + if (mCurrentContainer == mEndContainer && + (mCurrentEndOffset == -1 || mEndOffset <= mCurrentEndOffset)) { + return false; + } else { + mCurrentStartOffset = mCurrentEndOffset; + NormalizeForward(); + } + + int32_t nextLinkOffset = NextLinkOffset(); + if (mCurrentContainer == mEndContainer && + (nextLinkOffset == -1 || nextLinkOffset > mEndOffset)) { + mCurrentEndOffset = + mEndOffset < 0 ? mEndContainer->CharacterCount() : mEndOffset; + } else { + mCurrentEndOffset = nextLinkOffset < 0 ? mCurrentContainer->CharacterCount() + : nextLinkOffset; + } + + return mCurrentStartOffset != mCurrentEndOffset; +} + +void HyperTextAccessibleWrap::TextForRange(nsAString& aText, + int32_t aStartOffset, + HyperTextAccessible* aEndContainer, + int32_t aEndOffset) { + if (IsHTMLListItem()) { + Accessible* maybeBullet = GetChildAtOffset(aStartOffset - 1); + if (maybeBullet) { + Accessible* bullet = AsHTMLListItem()->Bullet(); + if (maybeBullet == bullet) { + TextSubstring(0, nsAccUtils::TextLength(bullet), aText); + } + } + } + + HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset); + while (iter.Next()) { + nsAutoString text; + iter.mCurrentContainer->TextSubstring(iter.mCurrentStartOffset, + iter.mCurrentEndOffset, text); + aText.Append(text); + } +} + +nsIntRect HyperTextAccessibleWrap::BoundsForRange( + int32_t aStartOffset, HyperTextAccessible* aEndContainer, + int32_t aEndOffset) { + nsIntRect rect; + HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset); + while (iter.Next()) { + nsIntRect stringRect = iter.mCurrentContainer->TextBounds( + iter.mCurrentStartOffset, iter.mCurrentEndOffset); + rect.UnionRect(rect, stringRect); + } + + return rect; +} + +int32_t HyperTextAccessibleWrap::LengthForRange( + int32_t aStartOffset, HyperTextAccessible* aEndContainer, + int32_t aEndOffset) { + int32_t length = 0; + HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset); + while (iter.Next()) { + length += iter.SegmentLength(); + } + + return length; +} + +void HyperTextAccessibleWrap::OffsetAtIndex(int32_t aIndex, + HyperTextAccessible** aContainer, + int32_t* aOffset) { + int32_t index = aIndex; + HyperTextIterator iter(this, 0, this, CharacterCount()); + while (iter.Next()) { + int32_t segmentLength = iter.SegmentLength(); + if (index <= segmentLength) { + *aContainer = iter.mCurrentContainer; + *aOffset = iter.mCurrentStartOffset + index; + break; + } + index -= segmentLength; + } +} + +void HyperTextAccessibleWrap::RangeAt(int32_t aOffset, EWhichRange aRangeType, + HyperTextAccessible** aStartContainer, + int32_t* aStartOffset, + HyperTextAccessible** aEndContainer, + int32_t* aEndOffset) { + switch (aRangeType) { + case EWhichRange::eLeftWord: + LeftWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer, + aEndOffset); + break; + case EWhichRange::eRightWord: + RightWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer, + aEndOffset); + break; + case EWhichRange::eLine: + case EWhichRange::eLeftLine: + LineAt(aOffset, false, aStartContainer, aStartOffset, aEndContainer, + aEndOffset); + break; + case EWhichRange::eRightLine: + LineAt(aOffset, true, aStartContainer, aStartOffset, aEndContainer, + aEndOffset); + break; + case EWhichRange::eParagraph: + ParagraphAt(aOffset, aStartContainer, aStartOffset, aEndContainer, + aEndOffset); + break; + case EWhichRange::eStyle: + StyleAt(aOffset, aStartContainer, aStartOffset, aEndContainer, + aEndOffset); + break; + default: + break; + } +} + +void HyperTextAccessibleWrap::LeftWordAt(int32_t aOffset, + HyperTextAccessible** aStartContainer, + int32_t* aStartOffset, + HyperTextAccessible** aEndContainer, + int32_t* aEndOffset) { + TextPoint here(this, aOffset); + TextPoint start = + FindTextPoint(aOffset, eDirPrevious, eSelectWord, eStartWord); + if (!start.mContainer) { + return; + } + + if ((NativeState() & states::EDITABLE) && + !(start.mContainer->NativeState() & states::EDITABLE)) { + // The word search crossed an editable boundary. Return the first word of + // the editable root. + return EditableRoot()->RightWordAt(0, aStartContainer, aStartOffset, + aEndContainer, aEndOffset); + } + + TextPoint end = + static_cast<HyperTextAccessibleWrap*>(start.mContainer) + ->FindTextPoint(start.mOffset, eDirNext, eSelectWord, eEndWord); + if (end < here) { + *aStartContainer = end.mContainer; + *aEndContainer = here.mContainer; + *aStartOffset = end.mOffset; + *aEndOffset = here.mOffset; + } else { + *aStartContainer = start.mContainer; + *aEndContainer = end.mContainer; + *aStartOffset = start.mOffset; + *aEndOffset = end.mOffset; + } +} + +void HyperTextAccessibleWrap::RightWordAt(int32_t aOffset, + HyperTextAccessible** aStartContainer, + int32_t* aStartOffset, + HyperTextAccessible** aEndContainer, + int32_t* aEndOffset) { + TextPoint here(this, aOffset); + TextPoint end = FindTextPoint(aOffset, eDirNext, eSelectWord, eEndWord); + if (!end.mContainer || end < here || here == end) { + // If we didn't find a word end, or if we wrapped around (bug 1652833), + // return with no result. + return; + } + + if ((NativeState() & states::EDITABLE) && + !(end.mContainer->NativeState() & states::EDITABLE)) { + // The word search crossed an editable boundary. Return with no result. + return; + } + + TextPoint start = + static_cast<HyperTextAccessibleWrap*>(end.mContainer) + ->FindTextPoint(end.mOffset, eDirPrevious, eSelectWord, eStartWord); + + if (here < start) { + *aStartContainer = here.mContainer; + *aEndContainer = start.mContainer; + *aStartOffset = here.mOffset; + *aEndOffset = start.mOffset; + } else { + *aStartContainer = start.mContainer; + *aEndContainer = end.mContainer; + *aStartOffset = start.mOffset; + *aEndOffset = end.mOffset; + } +} + +void HyperTextAccessibleWrap::LineAt(int32_t aOffset, bool aNextLine, + HyperTextAccessible** aStartContainer, + int32_t* aStartOffset, + HyperTextAccessible** aEndContainer, + int32_t* aEndOffset) { + TextPoint here(this, aOffset); + TextPoint end = + FindTextPoint(aOffset, eDirNext, eSelectEndLine, eDefaultBehavior); + if (!end.mContainer || end < here) { + // If we didn't find a word end, or if we wrapped around (bug 1652833), + // return with no result. + return; + } + + TextPoint start = static_cast<HyperTextAccessibleWrap*>(end.mContainer) + ->FindTextPoint(end.mOffset, eDirPrevious, + eSelectBeginLine, eDefaultBehavior); + + if (!aNextLine && here < start) { + start = FindTextPoint(aOffset, eDirPrevious, eSelectBeginLine, + eDefaultBehavior); + if (!start.mContainer) { + return; + } + + end = static_cast<HyperTextAccessibleWrap*>(start.mContainer) + ->FindTextPoint(start.mOffset, eDirNext, eSelectEndLine, + eDefaultBehavior); + } + + *aStartContainer = start.mContainer; + *aEndContainer = end.mContainer; + *aStartOffset = start.mOffset; + *aEndOffset = end.mOffset; +} + +void HyperTextAccessibleWrap::ParagraphAt(int32_t aOffset, + HyperTextAccessible** aStartContainer, + int32_t* aStartOffset, + HyperTextAccessible** aEndContainer, + int32_t* aEndOffset) { + TextPoint here(this, aOffset); + TextPoint end = + FindTextPoint(aOffset, eDirNext, eSelectParagraph, eDefaultBehavior); + + if (!end.mContainer || end < here) { + // If we didn't find a word end, or if we wrapped around (bug 1652833), + // return with no result. + return; + } + + if (end.mOffset == -1 && Parent() && Parent()->IsHyperText()) { + // If end offset is -1 we didn't find a paragraph boundary. + // This must be an inline container, go to its parent to + // retrieve paragraph boundaries. + static_cast<HyperTextAccessibleWrap*>(Parent()->AsHyperText()) + ->ParagraphAt(StartOffset(), aStartContainer, aStartOffset, + aEndContainer, aEndOffset); + return; + } + + TextPoint start = static_cast<HyperTextAccessibleWrap*>(end.mContainer) + ->FindTextPoint(end.mOffset, eDirPrevious, + eSelectParagraph, eDefaultBehavior); + + *aStartContainer = start.mContainer; + *aEndContainer = end.mContainer; + *aStartOffset = start.mOffset; + *aEndOffset = end.mOffset; +} + +void HyperTextAccessibleWrap::StyleAt(int32_t aOffset, + HyperTextAccessible** aStartContainer, + int32_t* aStartOffset, + HyperTextAccessible** aEndContainer, + int32_t* aEndOffset) { + // Get the range of the text leaf at this offset. + // A text leaf represents a stretch of like-styled text. + auto leaf = LeafAtOffset(aOffset); + if (!leaf) { + return; + } + + MOZ_ASSERT(leaf->Parent()->IsHyperText()); + HyperTextAccessibleWrap* container = + static_cast<HyperTextAccessibleWrap*>(leaf->Parent()->AsHyperText()); + if (!container) { + return; + } + + *aStartContainer = *aEndContainer = container; + container->RangeOfChild(leaf, aStartOffset, aEndOffset); +} + +void HyperTextAccessibleWrap::NextClusterAt( + int32_t aOffset, HyperTextAccessible** aNextContainer, + int32_t* aNextOffset) { + TextPoint here(this, aOffset); + TextPoint next = + FindTextPoint(aOffset, eDirNext, eSelectCluster, eDefaultBehavior); + + if ((next.mOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT && + next.mContainer == Document()) || + (next < here)) { + // If we reached the end of the doc, or if we wrapped to the start of the + // doc return given offset as-is. + *aNextContainer = this; + *aNextOffset = aOffset; + } else { + *aNextContainer = next.mContainer; + *aNextOffset = next.mOffset; + } +} + +void HyperTextAccessibleWrap::PreviousClusterAt( + int32_t aOffset, HyperTextAccessible** aPrevContainer, + int32_t* aPrevOffset) { + TextPoint prev = + FindTextPoint(aOffset, eDirPrevious, eSelectCluster, eDefaultBehavior); + *aPrevContainer = prev.mContainer; + *aPrevOffset = prev.mOffset; +} + +void HyperTextAccessibleWrap::RangeOfChild(Accessible* aChild, + int32_t* aStartOffset, + int32_t* aEndOffset) { + MOZ_ASSERT(aChild->Parent() == this); + *aStartOffset = *aEndOffset = -1; + int32_t index = GetIndexOf(aChild); + if (index != -1) { + *aStartOffset = GetChildOffset(index); + // If this is the last child index + 1 will return the total + // chracter count. + *aEndOffset = GetChildOffset(index + 1); + } +} + +Accessible* HyperTextAccessibleWrap::LeafAtOffset(int32_t aOffset) { + HyperTextAccessible* text = this; + Accessible* child = nullptr; + // The offset needed should "attach" the previous accessible if + // in between two accessibles. + int32_t innerOffset = aOffset > 0 ? aOffset - 1 : aOffset; + do { + int32_t childIdx = text->GetChildIndexAtOffset(innerOffset); + if (childIdx == -1) { + return text; + } + + child = text->GetChildAt(childIdx); + if (!child || nsAccUtils::MustPrune(text)) { + return text; + } + + innerOffset -= text->GetChildOffset(childIdx); + + text = child->AsHyperText(); + } while (text); + + return child; +} + +void HyperTextAccessibleWrap::SelectRange(int32_t aStartOffset, + HyperTextAccessible* aEndContainer, + int32_t aEndOffset) { + TextRange range(this, this, aStartOffset, aEndContainer, aEndOffset); + range.SetSelectionAt(0); +} + +TextPoint HyperTextAccessibleWrap::FindTextPoint( + int32_t aOffset, nsDirection aDirection, nsSelectionAmount aAmount, + EWordMovementType aWordMovementType) { + // Layout can remain trapped in an editable. We normalize out of + // it if we are in its last offset. + HyperTextIterator iter(this, aOffset, this, CharacterCount()); + if (aDirection == eDirNext) { + iter.NormalizeForward(); + } else { + iter.NormalizeBackward(); + } + + // Find a leaf accessible frame to start with. PeekOffset wants this. + HyperTextAccessible* text = iter.mCurrentContainer; + Accessible* child = nullptr; + int32_t innerOffset = iter.mCurrentStartOffset; + + do { + int32_t childIdx = text->GetChildIndexAtOffset(innerOffset); + + // We can have an empty text leaf as our only child. Since empty text + // leaves are not accessible we then have no children, but 0 is a valid + // innerOffset. + if (childIdx == -1) { + NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?"); + return TextPoint(text, 0); + } + + child = text->GetChildAt(childIdx); + if (child->IsHyperText() && !child->ChildCount()) { + // If this is a childless hypertext, jump to its + // previous or next sibling, depending on + // direction. + if (aDirection == eDirPrevious && childIdx > 0) { + child = text->GetChildAt(--childIdx); + } else if (aDirection == eDirNext && + childIdx + 1 < static_cast<int32_t>(text->ChildCount())) { + child = text->GetChildAt(++childIdx); + } + } + + int32_t childOffset = text->GetChildOffset(childIdx); + + if (child->IsHyperText() && aDirection == eDirPrevious && childIdx > 0 && + innerOffset - childOffset == 0) { + // If we are searching backwards, and this is the begining of a + // segment, get the previous sibling so that layout will start + // its search there. + childIdx--; + innerOffset -= text->GetChildOffset(childIdx); + child = text->GetChildAt(childIdx); + } else { + innerOffset -= childOffset; + } + + text = child->AsHyperText(); + } while (text); + + nsIFrame* childFrame = child->GetFrame(); + if (!childFrame) { + NS_ERROR("No child frame"); + return TextPoint(this, aOffset); + } + + int32_t innerContentOffset = innerOffset; + if (child->IsTextLeaf()) { + NS_ASSERTION(childFrame->IsTextFrame(), "Wrong frame!"); + RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset); + } + + nsIFrame* frameAtOffset = childFrame; + int32_t offsetInFrame = 0; + childFrame->GetChildFrameContainingOffset(innerContentOffset, true, + &offsetInFrame, &frameAtOffset); + if (aDirection == eDirPrevious && offsetInFrame == 0) { + // If we are searching backwards, and we are at the start of a frame, + // get the previous continuation frame. + if (nsIFrame* prevInContinuation = frameAtOffset->GetPrevContinuation()) { + frameAtOffset = prevInContinuation; + } + } + + const bool kIsJumpLinesOk = true; // okay to jump lines + const bool kIsScrollViewAStop = false; // do not stop at scroll views + const bool kIsKeyboardSelect = true; // is keyboard selection + const bool kIsVisualBidi = false; // use visual order for bidi text + nsPeekOffsetStruct pos( + aAmount, aDirection, innerContentOffset, nsPoint(0, 0), kIsJumpLinesOk, + kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi, false, + nsPeekOffsetStruct::ForceEditableRegion::No, aWordMovementType, false); + nsresult rv = frameAtOffset->PeekOffset(&pos); + + // PeekOffset fails on last/first lines of the text in certain cases. + if (NS_FAILED(rv) && aAmount == eSelectLine) { + pos.mAmount = aDirection == eDirNext ? eSelectEndLine : eSelectBeginLine; + frameAtOffset->PeekOffset(&pos); + } + if (!pos.mResultContent) { + NS_ERROR("No result content!"); + return TextPoint(this, aOffset); + } + + if (aDirection == eDirNext && + nsContentUtils::PositionIsBefore(pos.mResultContent, mContent, nullptr, + nullptr)) { + // Bug 1652833 makes us sometimes return the first element on the doc. + return TextPoint(Document(), nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT); + } + + HyperTextAccessible* container = + nsAccUtils::GetTextContainer(pos.mResultContent); + int32_t offset = container ? container->DOMPointToOffset( + pos.mResultContent, pos.mContentOffset, + aDirection == eDirNext) + : 0; + return TextPoint(container, offset); +} + +HyperTextAccessibleWrap* HyperTextAccessibleWrap::EditableRoot() { + Accessible* editable = nullptr; + for (Accessible* acc = this; acc && acc != Document(); acc = acc->Parent()) { + if (acc->NativeState() & states::EDITABLE) { + editable = acc; + } else { + break; + } + } + + return static_cast<HyperTextAccessibleWrap*>(editable->AsHyperText()); +} |