diff --git a/accessible/public/nsIAccessibleTextRange.idl b/accessible/public/nsIAccessibleTextRange.idl index 3e272e89a1b..3ceed19c847 100644 --- a/accessible/public/nsIAccessibleTextRange.idl +++ b/accessible/public/nsIAccessibleTextRange.idl @@ -6,20 +6,149 @@ #include "nsISupports.idl" interface nsIAccessible; +interface nsIAccessibleText; +interface nsIArray; +interface nsIVariant; /** * A range representing a piece of text in the document. */ -[scriptable, uuid(6fe17c33-6709-4d7a-9ba0-3d448c4b3ef4)] +[scriptable, uuid(525b3401-8a67-4822-b35d-661065767cd8)] interface nsIAccessibleTextRange : nsISupports { - readonly attribute nsIAccessible startContainer; + readonly attribute nsIAccessibleText startContainer; readonly attribute long startOffset; - readonly attribute nsIAccessible endContainer; + readonly attribute nsIAccessibleText endContainer; readonly attribute long endOffset; + /** + * Return an accessible containing the whole range + */ + readonly attribute nsIAccessible container; + + /** + * Return embedded children within the range. + */ + readonly attribute nsIArray embeddedChildren; + + /** + * Return true if this range has the same end points of the given range. + */ + boolean compare(in nsIAccessibleTextRange aOtherRange); + + /** + * The two endpoints of the range (starting and ending). + */ + const unsigned long EndPoint_Start = 1; + const unsigned long EndPoint_End = 2; + + /** + * Compare this and given ranges end points. + * + * @return -1/0/1 if this range end point is before/equal/after the given + * range end point. + */ + long compareEndPoints(in unsigned long aEndPoint, + in nsIAccessibleTextRange aOtherRange, + in unsigned long aOtherRangeEndPoint); + /** * Return text within the range. */ readonly attribute AString text; + + /** + * Return list of rects of the range. + */ + readonly attribute nsIArray bounds; + + const unsigned long FormatUnit = 0; + const unsigned long WordUnit = 1; + const unsigned long LineUnit = 2; + const unsigned long ParagraphUnit = 3; + const unsigned long PageUnit = 4; + const unsigned long DocumentUnit = 5; + + /** + * Move the boundary(ies) by the given number of the unit. + */ + void move(in unsigned long aUnit, in long aCount); + void moveStart(in unsigned long aUnit, in long aCount); + void moveEnd(in unsigned long aUnit, in long aCount); + + /** + * Normalize the range to the closest unit of the given type. + */ + void normalize(in unsigned long aUnit); + + /** + * Return range enclosing the found text. + */ + nsIAccessibleTextRange findText(in AString aText, in boolean aIsBackward, + in boolean aIsIgnoreCase); + + /** + * Text attributes. Used in conjunction with findAttrs(). + */ + const unsigned long AnimationStyleAttr = 0; + const unsigned long AnnotationObjectsAttr = 1; + const unsigned long AnnotationTypesAttr = 2; + const unsigned long BackgroundColorAttr = 3; + const unsigned long BulletStyleAttr = 4; + const unsigned long CapStyleAttr = 5; + const unsigned long CaretBidiModeAttr = 6; + const unsigned long CaretPositionAttr = 7; + const unsigned long CultureAttr = 8; + const unsigned long FontNameAttr = 9; + const unsigned long FontSizeAttr = 10; + const unsigned long FontWeightAttr = 11; + const unsigned long ForegroundColorAttr = 12; + const unsigned long HorizontalTextAlignmentAttr = 13; + const unsigned long IndentationFirstLineAttr = 14; + const unsigned long IndentationLeadingAttr = 15; + const unsigned long IndentationTrailingAttr = 16; + const unsigned long IsActiveAttr = 17; + const unsigned long IsHiddenAttr = 18; + const unsigned long IsItalicAttr = 19; + const unsigned long IsReadOnlyAttr = 20; + const unsigned long IsSubscriptAttr = 21; + const unsigned long IsSuperscriptAttr = 22; + const unsigned long LinkAttr = 23; + const unsigned long MarginBottomAttr = 24; + const unsigned long MarginLeadingAttr = 25; + const unsigned long MarginTopAttr = 26; + const unsigned long MarginTrailingAttr = 27; + const unsigned long OutlineStylesAttr = 28; + const unsigned long OverlineColorAttr = 29; + const unsigned long OverlineStyleAttr = 30; + const unsigned long SelectionActiveEndAttr = 31; + const unsigned long StrikethroughColorAttr = 32; + const unsigned long StrikethroughStyleAttr = 33; + const unsigned long StyleIdAttr = 34; + const unsigned long StyleNameAttr = 35; + const unsigned long TabsAttr = 36; + const unsigned long TextFlowDirectionsAttr = 37; + const unsigned long UnderlineColorAttr = 38; + const unsigned long UnderlineStyleAttr = 39; + + /** + * Return range enslosing the text having requested attribute. + */ + nsIAccessibleTextRange findAttr(in unsigned long aAttr, in nsIVariant aValue, + in boolean aIsBackward); + + /** + * Add/remove the text range from selection. + */ + void addToSelection(); + void removeFromSelection(); + void select(); + + const unsigned long AlignToTop = 0; + const unsigned long AlignToBottom = 1; + + /** + * Scroll the range into view. + */ + void scrollIntoView(in unsigned long aHow); }; diff --git a/accessible/src/base/TextRange.cpp b/accessible/src/base/TextRange.cpp index e3535f7c4c5..1707be3ea55 100644 --- a/accessible/src/base/TextRange.cpp +++ b/accessible/src/base/TextRange.cpp @@ -6,28 +6,239 @@ #include "TextRange.h" +#include "Accessible-inl.h" #include "HyperTextAccessible.h" +#include "nsAccUtils.h" using namespace mozilla::a11y; +//////////////////////////////////////////////////////////////////////////////// +// 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; + nsAutoTArray 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(); + } + + NS_ERROR("Broken tree?!"); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// TextRange + TextRange::TextRange(HyperTextAccessible* aRoot, - Accessible* aStartContainer, int32_t aStartOffset, - Accessible* aEndContainer, int32_t aEndOffset) : + HyperTextAccessible* aStartContainer, int32_t aStartOffset, + HyperTextAccessible* aEndContainer, int32_t aEndOffset) : mRoot(aRoot), mStartContainer(aStartContainer), mEndContainer(aEndContainer), mStartOffset(aStartOffset), mEndOffset(aEndOffset) { } +Accessible* +TextRange::Container() const +{ + if (mStartContainer == mEndContainer) + return mStartContainer; + + // Build the chain of parents + Accessible* p1 = mStartContainer; + Accessible* p2 = mEndContainer; + nsAutoTArray 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(); + uint32_t pos2 = parents2.Length(); + Accessible* parent = nullptr; + uint32_t len = 0; + for (len = std::min(pos1, pos2); len > 0; --len) { + Accessible* child1 = parents1.ElementAt(--pos1); + Accessible* child2 = parents2.ElementAt(--pos2); + if (child1 != child2) + break; + + parent = child1; + } + + return parent; +} + +void +TextRange::EmbeddedChildren(nsTArray* aChildren) const +{ + if (mStartContainer == mEndContainer) { + int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset); + int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset); + for (int32_t idx = startIdx; idx <= endIdx; idx++) { + Accessible* child = mStartContainer->GetChildAt(idx); + if (nsAccUtils::IsEmbeddedObject(child)) + aChildren->AppendElement(child); + } + return; + } + + Accessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset); + Accessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset); + nsAutoTArray parents1, parents2; + do { + parents1.AppendElement(p1); + p1 = p1->Parent(); + } while (p1); + do { + parents2.AppendElement(p2); + p2 = p2->Parent(); + } while (p2); + + // Find deepest common container. + uint32_t pos1 = parents1.Length(); + uint32_t pos2 = parents2.Length(); + Accessible* container = nullptr; + for (uint32_t len = std::min(pos1, pos2); len > 0; --len) { + Accessible* child1 = parents1.ElementAt(--pos1); + Accessible* child2 = parents2.ElementAt(--pos2); + if (child1 != child2) + break; + + container = child1; + } + + // Traverse the tree up to the container and collect embedded objects. + for (uint32_t idx = 0; idx < pos1 - 1; idx++) { + Accessible* parent = parents1[idx + 1]; + Accessible* child = parents1[idx]; + uint32_t childCount = parent->ChildCount(); + for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount; childIdx++) { + Accessible* next = parent->GetChildAt(childIdx); + if (nsAccUtils::IsEmbeddedObject(next)) + aChildren->AppendElement(next); + } + } + + // Traverse through direct children in the container. + int32_t endIdx = parents2[pos2 - 1]->IndexInParent(); + int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1; + for (; childIdx < endIdx; childIdx++) { + Accessible* next = container->GetChildAt(childIdx); + if (nsAccUtils::IsEmbeddedObject(next)) + aChildren->AppendElement(next); + } + + // Traverse down from the container to end point. + for (int32_t idx = pos2 - 2; idx > 0; idx--) { + Accessible* parent = parents2[idx]; + Accessible* child = parents2[idx - 1]; + int32_t endIdx = child->IndexInParent(); + for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) { + Accessible* next = parent->GetChildAt(childIdx); + if (nsAccUtils::IsEmbeddedObject(next)) + aChildren->AppendElement(next); + } + } +} + void TextRange::Text(nsAString& aText) const +{ + Accessible* current = mStartContainer->GetChildAtOffset(mStartOffset); + uint32_t startIntlOffset = + mStartOffset - mStartContainer->GetChildOffset(current); + + while (current && TextInternal(aText, current, startIntlOffset)) { + current = current->Parent(); + if (!current) + break; + + current = current->NextSibling(); + } +} + +void +TextRange::Bounds(nsTArray aRects) const { } +void +TextRange::Normalize(ETextUnit aUnit) +{ + +} + +void +TextRange::FindText(const nsAString& aText, EDirection aDirection, + nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const +{ + +} + +void +TextRange::FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection, + TextRange* aFoundRange) const +{ + +} + +void +TextRange::AddToSelection() const +{ + +} + +void +TextRange::RemoveFromSelection() const +{ + +} + +void +TextRange::Select() const +{ +} + +void +TextRange::ScrollIntoView(EHowToAlign aHow) const +{ + +} + +//////////////////////////////////////////////////////////////////////////////// +// pivate + void TextRange::Set(HyperTextAccessible* aRoot, - Accessible* aStartContainer, int32_t aStartOffset, - Accessible* aEndContainer, int32_t aEndOffset) + HyperTextAccessible* aStartContainer, int32_t aStartOffset, + HyperTextAccessible* aEndContainer, int32_t aEndOffset) { mRoot = aRoot; mStartContainer = aStartContainer; @@ -35,3 +246,51 @@ TextRange::Set(HyperTextAccessible* aRoot, mStartOffset = aStartOffset; mEndOffset = aEndOffset; } + +bool +TextRange::TextInternal(nsAString& aText, Accessible* aCurrent, + uint32_t aStartIntlOffset) const +{ + bool moveNext = true; + int32_t endIntlOffset = -1; + if (aCurrent->Parent() == mEndContainer && + mEndContainer->GetChildAtOffset(mEndOffset) == aCurrent) { + + uint32_t currentStartOffset = mEndContainer->GetChildOffset(aCurrent); + endIntlOffset = mEndOffset - currentStartOffset; + if (endIntlOffset == 0) + return false; + + moveNext = false; + } + + if (aCurrent->IsTextLeaf()) { + aCurrent->AppendTextTo(aText, aStartIntlOffset, + endIntlOffset - aStartIntlOffset); + if (!moveNext) + return false; + } + + Accessible* next = aCurrent->FirstChild(); + if (next) { + if (!TextInternal(aText, next, 0)) + return false; + } + + next = aCurrent->NextSibling(); + if (next) { + if (!TextInternal(aText, next, 0)) + return false; + } + + return moveNext; +} + + +void +TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount, + HyperTextAccessible& aContainer, int32_t aOffset, + HyperTextAccessible* aStopContainer, int32_t aStopOffset) +{ + +} diff --git a/accessible/src/base/TextRange.h b/accessible/src/base/TextRange.h index 169473db5c2..1c0d2c784b3 100644 --- a/accessible/src/base/TextRange.h +++ b/accessible/src/base/TextRange.h @@ -9,6 +9,11 @@ #include "mozilla/Move.h" #include "nsAutoPtr.h" +#include "nsCaseTreatment.h" +#include "nsRect.h" +#include "nsTArray.h" + + class nsIVariant; namespace mozilla { namespace a11y { @@ -16,6 +21,24 @@ namespace a11y { class Accessible; class HyperTextAccessible; +/** + * A text point (hyper text + offset), represents a boundary of text range. + */ +struct TextPoint MOZ_FINAL +{ + TextPoint(HyperTextAccessible* aContainer, int32_t aOffset) : + mContainer(aContainer), mOffset(aOffset) { } + TextPoint(const TextPoint& aPoint) : + mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) { } + + HyperTextAccessible* mContainer; + int32_t mOffset; + + bool operator ==(const TextPoint& aPoint) const + { return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset; } + bool operator <(const TextPoint& aPoint) const; +}; + /** * Represents a text range within the text control or document. */ @@ -23,39 +46,177 @@ class TextRange MOZ_FINAL { public: TextRange(HyperTextAccessible* aRoot, - Accessible* aStartContainer, int32_t aStartOffset, - Accessible* aEndContainer, int32_t aEndOffset); + HyperTextAccessible* aStartContainer, int32_t aStartOffset, + HyperTextAccessible* aEndContainer, int32_t aEndOffset); TextRange() {} TextRange(TextRange&& aRange) : - mRoot(Move(aRange.mRoot)), mStartContainer(Move(aRange.mStartContainer)), - mEndContainer(Move(aRange.mEndContainer)), + mRoot(mozilla::Move(aRange.mRoot)), + mStartContainer(mozilla::Move(aRange.mStartContainer)), + mEndContainer(mozilla::Move(aRange.mEndContainer)), mStartOffset(aRange.mStartOffset), mEndOffset(aRange.mEndOffset) {} TextRange& operator= (TextRange&& aRange) { - mRoot = Move(aRange.mRoot); - mStartContainer = Move(aRange.mStartContainer); - mEndContainer = Move(aRange.mEndContainer); + mRoot = mozilla::Move(aRange.mRoot); + mStartContainer = mozilla::Move(aRange.mStartContainer); + mEndContainer = mozilla::Move(aRange.mEndContainer); mStartOffset = aRange.mStartOffset; mEndOffset = aRange.mEndOffset; return *this; } - Accessible* StartContainer() const { return mStartContainer; } + HyperTextAccessible* StartContainer() const { return mStartContainer; } int32_t StartOffset() const { return mStartOffset; } - Accessible* EndContainer() const { return mEndContainer; } + HyperTextAccessible* EndContainer() const { return mEndContainer; } int32_t EndOffset() const { return mEndOffset; } + bool operator ==(const TextRange& aRange) const + { + return mStartContainer == aRange.mStartContainer && + mStartOffset == aRange.mStartOffset && + mEndContainer == aRange.mEndContainer && mEndOffset == aRange.mEndOffset; + } + + TextPoint StartPoint() const { return TextPoint(mStartContainer, mStartOffset); } + TextPoint EndPoint() const { return TextPoint(mEndContainer, mEndOffset); } + + /** + * Return a container containing both start and end points. + */ + Accessible* Container() const; + + /** + * Return a list of embedded objects enclosed by the text range (includes + * partially overlapped objects). + */ + void EmbeddedChildren(nsTArray* aChildren) const; + /** * Return text enclosed by the range. */ void Text(nsAString& aText) const; + /** + * Return list of bounding rects of the text range by lines. + */ + void Bounds(nsTArray aRects) const; + + enum ETextUnit { + eFormat, + eWord, + eLine, + eParagraph, + ePage, + eDocument + }; + + /** + * Move the range or its points on specified amount of given units. + */ + void Move(ETextUnit aUnit, int32_t aCount) + { + MoveEnd(aUnit, aCount); + MoveStart(aUnit, aCount); + } + void MoveStart(ETextUnit aUnit, int32_t aCount) + { + MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset, + mEndContainer, mEndOffset); + } + void MoveEnd(ETextUnit aUnit, int32_t aCount) + { MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset); } + + /** + * Move the range points to the closest unit boundaries. + */ + void Normalize(ETextUnit aUnit); + + enum EDirection { + eBackward, + eForward + }; + + /** + * Return range enclosing the found text. + */ + void FindText(const nsAString& aText, EDirection aDirection, + nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const; + + enum EAttr { + eAnimationStyleAttr, + eAnnotationObjectsAttr, + eAnnotationTypesAttr, + eBackgroundColorAttr, + eBulletStyleAttr, + eCapStyleAttr, + eCaretBidiModeAttr, + eCaretPositionAttr, + eCultureAttr, + eFontNameAttr, + eFontSizeAttr, + eFontWeightAttr, + eForegroundColorAttr, + eHorizontalTextAlignmentAttr, + eIndentationFirstLineAttr, + eIndentationLeadingAttr, + eIndentationTrailingAttr, + eIsActiveAttr, + eIsHiddenAttr, + eIsItalicAttr, + eIsReadOnlyAttr, + eIsSubscriptAttr, + eIsSuperscriptAttr, + eLinkAttr, + eMarginBottomAttr, + eMarginLeadingAttr, + eMarginTopAttr, + eMarginTrailingAttr, + eOutlineStylesAttr, + eOverlineColorAttr, + eOverlineStyleAttr, + eSelectionActiveEndAttr, + eStrikethroughColorAttr, + eStrikethroughStyleAttr, + eStyleIdAttr, + eStyleNameAttr, + eTabsAttr, + eTextFlowDirectionsAttr, + eUnderlineColorAttr, + eUnderlineStyleAttr + }; + + /** + * Return range enclosing text having requested attribute. + */ + void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection, + TextRange* aFoundRange) const; + + /** + * Add/remove the text range from selection. + */ + void AddToSelection() const; + void RemoveFromSelection() const; + void Select() const; + + /** + * Scroll the text range into view. + */ + enum EHowToAlign { + eAlignToTop, + eAlignToBottom + }; + void ScrollIntoView(EHowToAlign aHow) const; + /** * Return true if this TextRange object represents an actual range of text. */ bool IsValid() const { return mRoot; } + void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset) + { mStartContainer = aContainer; mStartOffset = aOffset; } + void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset) + { mStartContainer = aContainer; mStartOffset = aOffset; } + private: TextRange(const TextRange& aRange) MOZ_DELETE; TextRange& operator=(const TextRange& aRange) MOZ_DELETE; @@ -64,12 +225,27 @@ private: friend class xpcAccessibleTextRange; void Set(HyperTextAccessible* aRoot, - Accessible* aStartContainer, int32_t aStartOffset, - Accessible* aEndContainer, int32_t aEndOffset); + HyperTextAccessible* aStartContainer, int32_t aStartOffset, + HyperTextAccessible* aEndContainer, int32_t aEndOffset); + + /** + * Text() method helper. + * @param aText [in,out] calculated text + * @param aCurrent [in] currently traversed node + * @param aStartIntlOffset [in] start offset if current node is a text node + * @return true if calculation is not finished yet + */ + bool TextInternal(nsAString& aText, Accessible* aCurrent, + uint32_t aStartIntlOffset) const; + + void MoveInternal(ETextUnit aUnit, int32_t aCount, + HyperTextAccessible& aContainer, int32_t aOffset, + HyperTextAccessible* aStopContainer = nullptr, + int32_t aStopOffset = 0); nsRefPtr mRoot; - nsRefPtr mStartContainer; - nsRefPtr mEndContainer; + nsRefPtr mStartContainer; + nsRefPtr mEndContainer; int32_t mStartOffset; int32_t mEndOffset; }; diff --git a/accessible/src/generic/Accessible.cpp b/accessible/src/generic/Accessible.cpp index 7012e685923..fa18dea901c 100644 --- a/accessible/src/generic/Accessible.cpp +++ b/accessible/src/generic/Accessible.cpp @@ -120,7 +120,7 @@ Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) : #ifdef NS_DEBUG_X { nsCOMPtr shell(do_QueryReferent(aShell)); - printf(">>> %p Created Acc - DOM: %p PS: %p", + printf(">>> %p Created Acc - DOM: %p PS: %p", (void*)static_cast(this), (void*)aNode, (void*)shell.get()); nsCOMPtr content = do_QueryInterface(aNode); @@ -267,7 +267,7 @@ Accessible::Description(nsString& aDescription) // 1. it's a text node; or // 2. It has no DHTML describedby property // 3. it doesn't have an accName; or - // 4. its title attribute already equals to its accName nsAutoString name; + // 4. its title attribute already equals to its accName nsAutoString name; if (!HasOwnContent() || mContent->IsNodeOfType(nsINode::eTEXT)) return; @@ -550,7 +550,7 @@ Accessible::GetIndexInParent(int32_t* aIndexInParent) return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE; } -void +void Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) { nsCOMPtr stringBundleService = @@ -560,7 +560,7 @@ Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) nsCOMPtr stringBundle; stringBundleService->CreateBundle( - "chrome://global-platform/locale/accessible.properties", + "chrome://global-platform/locale/accessible.properties", getter_AddRefs(stringBundle)); if (!stringBundle) return; @@ -1104,7 +1104,7 @@ Accessible::XULElmName(DocAccessible* aDocument, itemEl->GetLabel(aName); } else { nsCOMPtr select = do_QueryInterface(aElm); - // Use label if this is not a select control element which + // Use label if this is not a select control element which // uses label attribute to indicate which option is selected if (!select) { nsCOMPtr xulEl(do_QueryInterface(aElm)); @@ -1578,7 +1578,7 @@ Accessible::ApplyARIAState(uint64_t* aState) const *aState |= states::UNAVAILABLE; break; } - } + } } // special case: A native button element whose role got transformed by ARIA to a toggle button @@ -2451,7 +2451,7 @@ Accessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, void Accessible::Shutdown() { - // Mark the accessible as defunct, invalidate the child count and pointers to + // Mark the accessible as defunct, invalidate the child count and pointers to // other accessibles, also make sure none of its children point to this parent mStateFlags |= eIsDefunct; @@ -3058,7 +3058,7 @@ Accessible::TestChildCache(Accessible* aCachedChild) const } NS_ASSERTION(child == aCachedChild, - "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!"); + "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!"); #endif } @@ -3135,7 +3135,7 @@ Accessible::GetActionRule() if (isOnclick) return eClickAction; - + // Get an action based on ARIA role. if (mRoleMapEntry && mRoleMapEntry->actionRule != eNoAction) diff --git a/accessible/src/generic/HyperTextAccessible.cpp b/accessible/src/generic/HyperTextAccessible.cpp index 9d3e5107950..a7ec4a10a6f 100644 --- a/accessible/src/generic/HyperTextAccessible.cpp +++ b/accessible/src/generic/HyperTextAccessible.cpp @@ -1551,9 +1551,9 @@ HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const { if (IsTextField()) { aRange.Set(mDoc, const_cast(this), 0, - const_cast(this), ChildCount()); + const_cast(this), CharacterCount()); } else { - aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->ChildCount()); + aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount()); } } @@ -1599,7 +1599,26 @@ void HyperTextAccessible::RangeByChild(Accessible* aChild, a11y::TextRange& aRange) const { - aRange.Set(mDoc, aChild, 0, aChild, aChild->ChildCount()); + HyperTextAccessible* ht = aChild->AsHyperText(); + if (ht) { + aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount()); + return; + } + + Accessible* child = aChild; + Accessible* parent = nullptr; + while ((parent = child->Parent()) && !(ht = parent->AsHyperText())) + child = parent; + + // If no text then return collapsed text range, otherwise return a range + // containing the text enclosed by the given child. + if (ht) { + int32_t childIdx = child->IndexInParent(); + int32_t startOffset = ht->GetChildOffset(childIdx); + int32_t endOffset = child->IsTextLeaf() ? + ht->GetChildOffset(childIdx + 1) : startOffset; + aRange.Set(mDoc, ht, startOffset, ht, endOffset); + } } void @@ -1607,8 +1626,19 @@ HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY, a11y::TextRange& aRange) const { Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild); - if (child) - aRange.Set(mDoc, child, 0, child, child->ChildCount()); + if (!child) + return; + + Accessible* parent = nullptr; + while ((parent = child->Parent()) && !parent->IsHyperText()) + child = parent; + + // Return collapsed text range for the point. + if (parent) { + HyperTextAccessible* ht = parent->AsHyperText(); + int32_t offset = ht->GetChildOffset(child); + aRange.Set(mDoc, ht, offset, ht, offset); + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/xpcom/xpcAccessibleTextRange.cpp b/accessible/src/xpcom/xpcAccessibleTextRange.cpp index 81bac51a64b..51226e4f866 100644 --- a/accessible/src/xpcom/xpcAccessibleTextRange.cpp +++ b/accessible/src/xpcom/xpcAccessibleTextRange.cpp @@ -9,6 +9,8 @@ #include "HyperTextAccessible.h" #include "TextRange.h" + #include "nsIMutableArray.h" + using namespace mozilla; using namespace mozilla::a11y; @@ -21,6 +23,7 @@ NS_IMPL_CYCLE_COLLECTION(xpcAccessibleTextRange, NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(xpcAccessibleTextRange) NS_INTERFACE_MAP_ENTRY(nsIAccessibleTextRange) + NS_INTERFACE_MAP_ENTRY(xpcAccessibleTextRange) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleTextRange) NS_INTERFACE_MAP_END @@ -30,10 +33,10 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleTextRange) // nsIAccessibleTextRange NS_IMETHODIMP -xpcAccessibleTextRange::GetStartContainer(nsIAccessible** aAnchor) +xpcAccessibleTextRange::GetStartContainer(nsIAccessibleText** aAnchor) { NS_ENSURE_ARG_POINTER(aAnchor); - NS_IF_ADDREF(*aAnchor = static_cast(mRange.StartContainer())); + NS_IF_ADDREF(*aAnchor = static_cast(mRange.StartContainer())); return NS_OK; } @@ -46,10 +49,10 @@ xpcAccessibleTextRange::GetStartOffset(int32_t* aOffset) } NS_IMETHODIMP -xpcAccessibleTextRange::GetEndContainer(nsIAccessible** aAnchor) +xpcAccessibleTextRange::GetEndContainer(nsIAccessibleText** aAnchor) { NS_ENSURE_ARG_POINTER(aAnchor); - NS_IF_ADDREF(*aAnchor = static_cast(mRange.EndContainer())); + NS_IF_ADDREF(*aAnchor = static_cast(mRange.EndContainer())); return NS_OK; } @@ -61,6 +64,70 @@ xpcAccessibleTextRange::GetEndOffset(int32_t* aOffset) return NS_OK; } +NS_IMETHODIMP +xpcAccessibleTextRange::GetContainer(nsIAccessible** aContainer) +{ + NS_ENSURE_ARG_POINTER(aContainer); + NS_IF_ADDREF(*aContainer = static_cast(mRange.Container())); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::GetEmbeddedChildren(nsIArray** aList) +{ + nsresult rv = NS_OK; + nsCOMPtr xpcList = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray objects; + mRange.EmbeddedChildren(&objects); + + uint32_t len = objects.Length(); + for (uint32_t idx = 0; idx < len; idx++) + xpcList->AppendElement(static_cast(objects[idx]), false); + + xpcList.forget(aList); + + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::Compare(nsIAccessibleTextRange* aOtherRange, + bool* aResult) +{ + + nsRefPtr xpcRange(do_QueryObject(aOtherRange)); + if (!xpcRange || !aResult) + return NS_ERROR_INVALID_ARG; + + *aResult = (mRange == xpcRange->mRange); + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::CompareEndPoints(uint32_t aEndPoint, + nsIAccessibleTextRange* aOtherRange, + uint32_t aOtherRangeEndPoint, + int32_t* aResult) +{ + nsRefPtr xpcRange(do_QueryObject(aOtherRange)); + if (!xpcRange || !aResult) + return NS_ERROR_INVALID_ARG; + + TextPoint p = (aEndPoint == EndPoint_Start) ? + mRange.StartPoint() : mRange.EndPoint(); + TextPoint otherPoint = (aOtherRangeEndPoint == EndPoint_Start) ? + xpcRange->mRange.StartPoint() : xpcRange->mRange.EndPoint(); + + if (p == otherPoint) + *aResult = 0; + else + *aResult = p < otherPoint ? -1 : 1; + + return NS_OK; +} + NS_IMETHODIMP xpcAccessibleTextRange::GetText(nsAString& aText) { @@ -70,3 +137,73 @@ xpcAccessibleTextRange::GetText(nsAString& aText) return NS_OK; } + +NS_IMETHODIMP +xpcAccessibleTextRange::GetBounds(nsIArray** aRectList) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::Move(uint32_t aUnit, int32_t aCount) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::MoveStart(uint32_t aUnit, int32_t aCount) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::MoveEnd(uint32_t aUnit, int32_t aCount) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::Normalize(uint32_t aUnit) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::FindText(const nsAString& aText, bool aIsBackward, + bool aIsIgnoreCase, + nsIAccessibleTextRange** aRange) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::FindAttr(uint32_t aAttr, nsIVariant* aVal, + bool aIsBackward, + nsIAccessibleTextRange** aRange) +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::AddToSelection() +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::RemoveFromSelection() +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::Select() +{ + return NS_OK; +} + +NS_IMETHODIMP +xpcAccessibleTextRange::ScrollIntoView(uint32_t aHow) +{ + return NS_OK; +} diff --git a/accessible/src/xpcom/xpcAccessibleTextRange.h b/accessible/src/xpcom/xpcAccessibleTextRange.h index 6556706c062..762b1588333 100644 --- a/accessible/src/xpcom/xpcAccessibleTextRange.h +++ b/accessible/src/xpcom/xpcAccessibleTextRange.h @@ -18,17 +18,47 @@ namespace a11y { class TextRange; +#define NS_ACCESSIBLETEXTRANGE_IMPL_IID \ +{ /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */ \ + 0xb17652d9, \ + 0x4f54, \ + 0x4c56, \ + { 0xbb, 0x62, 0x6d, 0x5b, 0xf1, 0xef, 0x91, 0x0c } \ +} + class xpcAccessibleTextRange MOZ_FINAL : public nsIAccessibleTextRange { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(xpcAccessibleTextRange) - NS_IMETHOD GetStartContainer(nsIAccessible** aAnchor) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD GetStartContainer(nsIAccessibleText** aAnchor) MOZ_FINAL MOZ_OVERRIDE; NS_IMETHOD GetStartOffset(int32_t* aOffset) MOZ_FINAL MOZ_OVERRIDE; - NS_IMETHOD GetEndContainer(nsIAccessible** aAnchor) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD GetEndContainer(nsIAccessibleText** aAnchor) MOZ_FINAL MOZ_OVERRIDE; NS_IMETHOD GetEndOffset(int32_t* aOffset) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD GetContainer(nsIAccessible** aContainer) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD GetEmbeddedChildren(nsIArray** aList) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD Compare(nsIAccessibleTextRange* aOtherRange, bool* aResult) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD CompareEndPoints(uint32_t aEndPoint, + nsIAccessibleTextRange* aOtherRange, + uint32_t aOtherRangeEndPoint, + int32_t* aResult) MOZ_FINAL MOZ_OVERRIDE; NS_IMETHOD GetText(nsAString& aText) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD GetBounds(nsIArray** aRectList) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD Move(uint32_t aUnit, int32_t aCount) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD MoveStart(uint32_t aUnit, int32_t aCount) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD MoveEnd(uint32_t aUnit, int32_t aCount) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD Normalize(uint32_t aUnit) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD FindText(const nsAString& aText, bool aIsBackward, bool aIsIgnoreCase, + nsIAccessibleTextRange** aRange) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD FindAttr(uint32_t aAttr, nsIVariant* aVal, bool aIsBackward, + nsIAccessibleTextRange** aRange) MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD AddToSelection() MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD RemoveFromSelection() MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD Select() MOZ_FINAL MOZ_OVERRIDE; + NS_IMETHOD ScrollIntoView(uint32_t aHow) MOZ_FINAL MOZ_OVERRIDE; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLETEXTRANGE_IMPL_IID) private: xpcAccessibleTextRange(TextRange&& aRange) : @@ -42,6 +72,9 @@ private: TextRange mRange; }; +NS_DEFINE_STATIC_IID_ACCESSOR(xpcAccessibleTextRange, + NS_ACCESSIBLETEXTRANGE_IMPL_IID) + } // namespace a11y } // namespace mozilla diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js index 6a33e6798ed..2851fbfd7a9 100644 --- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -19,6 +19,7 @@ const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType; const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType; const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation; +const nsIAccessibleTextRange = Components.interfaces.nsIAccessibleTextRange; const nsIAccessible = Components.interfaces.nsIAccessible; @@ -434,7 +435,7 @@ function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) } break; - } + } default: if (prop.indexOf("todo_") == 0) diff --git a/accessible/tests/mochitest/text.js b/accessible/tests/mochitest/text.js index 17e085ddeec..7b5f86b0cf1 100644 --- a/accessible/tests/mochitest/text.js +++ b/accessible/tests/mochitest/text.js @@ -10,6 +10,9 @@ const BOUNDARY_LINE_END = nsIAccessibleText.BOUNDARY_LINE_END; const kTextEndOffset = nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT; const kCaretOffset = nsIAccessibleText.TEXT_OFFSET_CARET; +const EndPoint_Start = nsIAccessibleTextRange.EndPoint_Start; +const EndPoint_End = nsIAccessibleTextRange.EndPoint_End; + const kTodo = 1; // a test is expected to fail const kOk = 2; // a test doesn't fail @@ -393,8 +396,8 @@ function testTextRemoveSelection(aID, aSelectionIndex, aSelectionsCount) acc.removeSelection(aSelectionIndex); - ok(acc.selectionCount, aSelectionsCount, - text + ": failed to remove selection at index '" + + ok(acc.selectionCount, aSelectionsCount, + text + ": failed to remove selection at index '" + aSelectionIndex + "': selectionCount after"); } @@ -416,8 +419,8 @@ function testTextSetSelection(aID, aStartOffset, aEndOffset, acc.setSelectionBounds(aSelectionIndex, aStartOffset, aEndOffset); - is(acc.selectionCount, aSelectionsCount, - text + ": failed to set selection at index '" + + is(acc.selectionCount, aSelectionsCount, + text + ": failed to set selection at index '" + aSelectionIndex + "': selectionCount after"); } @@ -457,17 +460,36 @@ function testTextGetSelection(aID, aStartOffset, aEndOffset, aSelectionIndex) aSelectionIndex + "'"); } -function testTextRange(aRange, aStartContainer, aStartOffset, - aEndContainer, aEndOffset) +function testTextRange(aRange, aRangeDescr, aStartContainer, aStartOffset, + aEndContainer, aEndOffset, aText, + aCommonContainer, aChildren) { - is(aRange.startContainer, getAccessible(aStartContainer), - "Wrong start container"); + isObject(aRange.startContainer, getAccessible(aStartContainer), + "Wrong start container of " + aRangeDescr); is(aRange.startOffset, aStartOffset, - "Wrong start offset"); - is(aRange.endContainer, getAccessible(aEndContainer), - "Wrong end container"); + "Wrong start offset of " + aRangeDescr); + isObject(aRange.endContainer, getAccessible(aEndContainer), + "Wrong end container of " + aRangeDescr); is(aRange.endOffset, aEndOffset, - "Wrong end offset"); + "Wrong end offset of " + aRangeDescr); + + is(aRange.text, aText, "Wrong text of " + aRangeDescr); + + var children = aRange.embeddedChildren; + is(children ? children.length : 0, aChildren ? aChildren.length : 0, + "Wrong embedded children count of " + aRangeDescr); + + isObject(aRange.container, getAccessible(aCommonContainer), + "Wrong container of " + aRangeDescr); + + if (aChildren) { + for (var i = 0; i < aChildren.length; i++) { + var expectedChild = getAccessible(aChildren[i]); + var actualChild = children.queryElementAt(i, nsIAccessible); + isObject(actualChild, expectedChild, + "Wrong child at index '" + i + "' of " + aRangeDescr); + } + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/tests/mochitest/textrange/test_general.html b/accessible/tests/mochitest/textrange/test_general.html index c5cb1bb8243..21a758e1725 100644 --- a/accessible/tests/mochitest/textrange/test_general.html +++ b/accessible/tests/mochitest/textrange/test_general.html @@ -11,19 +11,75 @@ src="../common.js"> +