12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231 |
- /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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 "HyperTextAccessible-inl.h"
- #include "Accessible-inl.h"
- #include "nsAccessibilityService.h"
- #include "nsIAccessibleTypes.h"
- #include "DocAccessible.h"
- #include "HTMLListAccessible.h"
- #include "Relation.h"
- #include "Role.h"
- #include "States.h"
- #include "TextAttrs.h"
- #include "TextRange.h"
- #include "TreeWalker.h"
- #include "nsCaret.h"
- #include "nsContentUtils.h"
- #include "nsFocusManager.h"
- #include "nsIDOMRange.h"
- #include "nsIEditingSession.h"
- #include "nsContainerFrame.h"
- #include "nsFrameSelection.h"
- #include "nsILineIterator.h"
- #include "nsIInterfaceRequestorUtils.h"
- #include "nsIPersistentProperties2.h"
- #include "nsIScrollableFrame.h"
- #include "nsIServiceManager.h"
- #include "nsITextControlElement.h"
- #include "nsIMathMLFrame.h"
- #include "nsTextFragment.h"
- #include "mozilla/BinarySearch.h"
- #include "mozilla/dom/Element.h"
- #include "mozilla/EventStates.h"
- #include "mozilla/dom/Selection.h"
- #include "mozilla/MathAlgorithms.h"
- #include "gfxSkipChars.h"
- #include <algorithm>
- using namespace mozilla;
- using namespace mozilla::a11y;
- ////////////////////////////////////////////////////////////////////////////////
- // HyperTextAccessible
- ////////////////////////////////////////////////////////////////////////////////
- HyperTextAccessible::
- HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) :
- AccessibleWrap(aNode, aDoc)
- {
- mType = eHyperTextType;
- mGenericTypes |= eHyperText;
- }
- NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessible, Accessible)
- role
- HyperTextAccessible::NativeRole()
- {
- a11y::role r = GetAccService()->MarkupRole(mContent);
- if (r != roles::NOTHING)
- return r;
- nsIFrame* frame = GetFrame();
- if (frame && frame->GetType() == nsGkAtoms::inlineFrame)
- return roles::TEXT;
- return roles::TEXT_CONTAINER;
- }
- uint64_t
- HyperTextAccessible::NativeState()
- {
- uint64_t states = AccessibleWrap::NativeState();
- if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
- states |= states::EDITABLE;
- } else if (mContent->IsHTMLElement(nsGkAtoms::article)) {
- // We want <article> to behave like a document in terms of readonly state.
- states |= states::READONLY;
- }
- if (HasChildren())
- states |= states::SELECTABLE_TEXT;
- return states;
- }
- nsIntRect
- HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame,
- uint32_t aStartRenderedOffset,
- uint32_t aEndRenderedOffset)
- {
- nsPresContext* presContext = mDoc->PresContext();
- if (aFrame->GetType() != nsGkAtoms::textFrame) {
- return aFrame->GetScreenRectInAppUnits().
- ToNearestPixels(presContext->AppUnitsPerDevPixel());
- }
- // Substring must be entirely within the same text node.
- int32_t startContentOffset, endContentOffset;
- nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset);
- NS_ENSURE_SUCCESS(rv, nsIntRect());
- rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
- NS_ENSURE_SUCCESS(rv, nsIntRect());
- nsIFrame *frame;
- int32_t startContentOffsetInFrame;
- // Get the right frame continuation -- not really a child, but a sibling of
- // the primary frame passed in
- rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false,
- &startContentOffsetInFrame, &frame);
- NS_ENSURE_SUCCESS(rv, nsIntRect());
- nsRect screenRect;
- while (frame && startContentOffset < endContentOffset) {
- // Start with this frame's screen rect, which we will shrink based on
- // the substring we care about within it. We will then add that frame to
- // the total screenRect we are returning.
- nsRect frameScreenRect = frame->GetScreenRectInAppUnits();
- // Get the length of the substring in this frame that we want the bounds for
- int32_t startFrameTextOffset, endFrameTextOffset;
- frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
- int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
- int32_t seekLength = endContentOffset - startContentOffset;
- int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength);
- // Add the point where the string starts to the frameScreenRect
- nsPoint frameTextStartPoint;
- rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
- NS_ENSURE_SUCCESS(rv, nsIntRect());
- // Use the point for the end offset to calculate the width
- nsPoint frameTextEndPoint;
- rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
- NS_ENSURE_SUCCESS(rv, nsIntRect());
- frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x);
- frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x);
- screenRect.UnionRect(frameScreenRect, screenRect);
- // Get ready to loop back for next frame continuation
- startContentOffset += frameSubStringLength;
- startContentOffsetInFrame = 0;
- frame = frame->GetNextContinuation();
- }
- return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
- }
- void
- HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
- nsAString& aText)
- {
- aText.Truncate();
- index_t startOffset = ConvertMagicOffset(aStartOffset);
- index_t endOffset = ConvertMagicOffset(aEndOffset);
- if (!startOffset.IsValid() || !endOffset.IsValid() ||
- startOffset > endOffset || endOffset > CharacterCount()) {
- NS_ERROR("Wrong in offset");
- return;
- }
- int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
- if (startChildIdx == -1)
- return;
- int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
- if (endChildIdx == -1)
- return;
- if (startChildIdx == endChildIdx) {
- int32_t childOffset = GetChildOffset(startChildIdx);
- if (childOffset == -1)
- return;
- Accessible* child = GetChildAt(startChildIdx);
- child->AppendTextTo(aText, startOffset - childOffset,
- endOffset - startOffset);
- return;
- }
- int32_t startChildOffset = GetChildOffset(startChildIdx);
- if (startChildOffset == -1)
- return;
- Accessible* startChild = GetChildAt(startChildIdx);
- startChild->AppendTextTo(aText, startOffset - startChildOffset);
- for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) {
- Accessible* child = GetChildAt(childIdx);
- child->AppendTextTo(aText);
- }
- int32_t endChildOffset = GetChildOffset(endChildIdx);
- if (endChildOffset == -1)
- return;
- Accessible* endChild = GetChildAt(endChildIdx);
- endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
- }
- uint32_t
- HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
- bool aIsEndOffset) const
- {
- if (!aNode)
- return 0;
- uint32_t offset = 0;
- nsINode* findNode = nullptr;
- if (aNodeOffset == -1) {
- findNode = aNode;
- } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
- // For text nodes, aNodeOffset comes in as a character offset
- // Text offset will be added at the end, if we find the offset in this hypertext
- // We want the "skipped" offset into the text (rendered text without the extra whitespace)
- nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
- NS_ENSURE_TRUE(frame, 0);
- nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
- NS_ENSURE_SUCCESS(rv, 0);
- findNode = aNode;
- } else {
- // findNode could be null if aNodeOffset == # of child nodes, which means
- // one of two things:
- // 1) there are no children, and the passed-in node is not mContent -- use
- // parentContent for the node to find
- // 2) there are no children and the passed-in node is mContent, which means
- // we're an empty nsIAccessibleText
- // 3) there are children and we're at the end of the children
- findNode = aNode->GetChildAt(aNodeOffset);
- if (!findNode) {
- if (aNodeOffset == 0) {
- if (aNode == GetNode()) {
- // Case #1: this accessible has no children and thus has empty text,
- // we can only be at hypertext offset 0.
- return 0;
- }
- // Case #2: there are no children, we're at this node.
- findNode = aNode;
- } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) {
- // Case #3: we're after the last child, get next node to this one.
- for (nsINode* tmpNode = aNode;
- !findNode && tmpNode && tmpNode != mContent;
- tmpNode = tmpNode->GetParent()) {
- findNode = tmpNode->GetNextSibling();
- }
- }
- }
- }
- // Get accessible for this findNode, or if that node isn't accessible, use the
- // accessible for the next DOM node which has one (based on forward depth first search)
- Accessible* descendant = nullptr;
- if (findNode) {
- nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode));
- if (findContent && findContent->IsHTMLElement() &&
- findContent->NodeInfo()->Equals(nsGkAtoms::br) &&
- findContent->AttrValueIs(kNameSpaceID_None,
- nsGkAtoms::mozeditorbogusnode,
- nsGkAtoms::_true,
- eIgnoreCase)) {
- // This <br> is the hacky "bogus node" used when there is no text in a control
- return 0;
- }
- descendant = mDoc->GetAccessible(findNode);
- if (!descendant && findNode->IsContent()) {
- Accessible* container = mDoc->GetContainerAccessible(findNode);
- if (container) {
- TreeWalker walker(container, findNode->AsContent(),
- TreeWalker::eWalkContextTree);
- descendant = walker.Next();
- if (!descendant)
- descendant = container;
- }
- }
- }
- return TransformOffset(descendant, offset, aIsEndOffset);
- }
- uint32_t
- HyperTextAccessible::TransformOffset(Accessible* aDescendant,
- uint32_t aOffset, bool aIsEndOffset) const
- {
- // From the descendant, go up and get the immediate child of this hypertext.
- uint32_t offset = aOffset;
- Accessible* descendant = aDescendant;
- while (descendant) {
- Accessible* parent = descendant->Parent();
- if (parent == this)
- return GetChildOffset(descendant) + offset;
- // This offset no longer applies because the passed-in text object is not
- // a child of the hypertext. This happens when there are nested hypertexts,
- // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
- // to make it relative the hypertext.
- // If the end offset is not supposed to be inclusive and the original point
- // is not at 0 offset then the returned offset should be after an embedded
- // character the original point belongs to.
- if (aIsEndOffset)
- offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
- else
- offset = 0;
- descendant = parent;
- }
- // If the given a11y point cannot be mapped into offset relative this hypertext
- // offset then return length as fallback value.
- return CharacterCount();
- }
- /**
- * GetElementAsContentOf() returns a content representing an element which is
- * or includes aNode.
- *
- * XXX This method is enough to retrieve ::before or ::after pseudo element.
- * So, if you want to use this for other purpose, you might need to check
- * ancestors too.
- */
- static nsIContent* GetElementAsContentOf(nsINode* aNode)
- {
- if (aNode->IsElement()) {
- return aNode->AsContent();
- }
- nsIContent* parent = aNode->GetParent();
- return parent && parent->IsElement() ? parent : nullptr;
- }
- bool
- HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
- nsRange* aRange)
- {
- DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
- if (!startPoint.node)
- return false;
- // HyperTextAccessible manages pseudo elements generated by ::before or
- // ::after. However, contents of them are not in the DOM tree normally.
- // Therefore, they are not selectable and editable. So, when this creates
- // a DOM range, it should not start from nor end in any pseudo contents.
- nsIContent* container = GetElementAsContentOf(startPoint.node);
- DOMPoint startPointForDOMRange =
- ClosestNotGeneratedDOMPoint(startPoint, container);
- aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
- // If the caller wants collapsed range, let's collapse the range to its start.
- if (aStartOffset == aEndOffset) {
- aRange->Collapse(true);
- return true;
- }
- DOMPoint endPoint = OffsetToDOMPoint(aEndOffset);
- if (!endPoint.node)
- return false;
- if (startPoint.node != endPoint.node) {
- container = GetElementAsContentOf(endPoint.node);
- }
- DOMPoint endPointForDOMRange =
- ClosestNotGeneratedDOMPoint(endPoint, container);
- aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
- return true;
- }
- DOMPoint
- HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset)
- {
- // 0 offset is valid even if no children. In this case the associated editor
- // is empty so return a DOM point for editor root element.
- if (aOffset == 0) {
- nsCOMPtr<nsIEditor> editor = GetEditor();
- if (editor) {
- bool isEmpty = false;
- editor->GetDocumentIsEmpty(&isEmpty);
- if (isEmpty) {
- nsCOMPtr<nsIDOMElement> editorRootElm;
- editor->GetRootElement(getter_AddRefs(editorRootElm));
- nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
- return DOMPoint(editorRoot, 0);
- }
- }
- }
- int32_t childIdx = GetChildIndexAtOffset(aOffset);
- if (childIdx == -1)
- return DOMPoint();
- Accessible* child = GetChildAt(childIdx);
- int32_t innerOffset = aOffset - GetChildOffset(childIdx);
- // A text leaf case.
- if (child->IsTextLeaf()) {
- // The point is inside the text node. This is always true for any text leaf
- // except a last child one. See assertion below.
- if (aOffset < GetChildOffset(childIdx + 1)) {
- nsIContent* content = child->GetContent();
- int32_t idx = 0;
- if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
- innerOffset, &idx)))
- return DOMPoint();
- return DOMPoint(content, idx);
- }
- // Set the DOM point right after the text node.
- MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
- innerOffset = 1;
- }
- // Case of embedded object. The point is either before or after the element.
- NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
- nsINode* node = child->GetNode();
- nsINode* parentNode = node->GetParentNode();
- return parentNode ?
- DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
- DOMPoint();
- }
- DOMPoint
- HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
- nsIContent* aElementContent)
- {
- MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
- // ::before pseudo element
- if (aElementContent &&
- aElementContent->IsGeneratedContentContainerForBefore()) {
- MOZ_ASSERT(aElementContent->GetParent(),
- "::before must have parent element");
- // The first child of its parent (i.e., immediately after the ::before) is
- // good point for a DOM range.
- return DOMPoint(aElementContent->GetParent(), 0);
- }
- // ::after pseudo element
- if (aElementContent &&
- aElementContent->IsGeneratedContentContainerForAfter()) {
- MOZ_ASSERT(aElementContent->GetParent(),
- "::after must have parent element");
- // The end of its parent (i.e., immediately before the ::after) is good
- // point for a DOM range.
- return DOMPoint(aElementContent->GetParent(),
- aElementContent->GetParent()->GetChildCount());
- }
- return aDOMPoint;
- }
- uint32_t
- HyperTextAccessible::FindOffset(uint32_t aOffset, nsDirection aDirection,
- nsSelectionAmount aAmount,
- EWordMovementType aWordMovementType)
- {
- NS_ASSERTION(aDirection == eDirPrevious || aAmount != eSelectBeginLine,
- "eSelectBeginLine should only be used with eDirPrevious");
- // Find a leaf accessible frame to start with. PeekOffset wants this.
- HyperTextAccessible* text = this;
- Accessible* child = nullptr;
- int32_t innerOffset = aOffset;
- 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 DOMPointToOffset(text->GetNode(), 0, aDirection == eDirNext);
- }
- child = text->GetChildAt(childIdx);
- // HTML list items may need special processing because PeekOffset doesn't
- // work with list bullets.
- if (text->IsHTMLListItem()) {
- HTMLLIAccessible* li = text->AsHTMLListItem();
- if (child == li->Bullet()) {
- // XXX: the logic is broken for multichar bullets in moving by
- // char/cluster/word cases.
- if (text != this) {
- return aDirection == eDirPrevious ?
- TransformOffset(text, 0, false) :
- TransformOffset(text, 1, true);
- }
- if (aDirection == eDirPrevious)
- return 0;
- uint32_t nextOffset = GetChildOffset(1);
- if (nextOffset == 0)
- return 0;
- switch (aAmount) {
- case eSelectLine:
- case eSelectEndLine:
- // Ask a text leaf next (if not empty) to the bullet for an offset
- // since list item may be multiline.
- return nextOffset < CharacterCount() ?
- FindOffset(nextOffset, aDirection, aAmount, aWordMovementType) :
- nextOffset;
- default:
- return nextOffset;
- }
- }
- }
- innerOffset -= text->GetChildOffset(childIdx);
- text = child->AsHyperText();
- } while (text);
- nsIFrame* childFrame = child->GetFrame();
- if (!childFrame) {
- NS_ERROR("No child frame");
- return 0;
- }
- int32_t innerContentOffset = innerOffset;
- if (child->IsTextLeaf()) {
- NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!");
- RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
- }
- nsIFrame* frameAtOffset = childFrame;
- int32_t unusedOffsetInFrame = 0;
- childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
- &unusedOffsetInFrame,
- &frameAtOffset);
- 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, aWordMovementType);
- 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 0;
- }
- // Turn the resulting DOM point into an offset.
- uint32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
- pos.mContentOffset,
- aDirection == eDirNext);
- if (aDirection == eDirPrevious) {
- // If we reached the end during search, this means we didn't find the DOM point
- // and we're actually at the start of the paragraph
- if (hyperTextOffset == CharacterCount())
- return 0;
- // PeekOffset stops right before bullet so return 0 to workaround it.
- if (IsHTMLListItem() && aAmount == eSelectBeginLine &&
- hyperTextOffset > 0) {
- Accessible* prevOffsetChild = GetChildAtOffset(hyperTextOffset - 1);
- if (prevOffsetChild == AsHTMLListItem()->Bullet())
- return 0;
- }
- }
- return hyperTextOffset;
- }
- uint32_t
- HyperTextAccessible::FindLineBoundary(uint32_t aOffset,
- EWhichLineBoundary aWhichLineBoundary)
- {
- // Note: empty last line doesn't have own frame (a previous line contains '\n'
- // character instead) thus when it makes a difference we need to process this
- // case separately (otherwise operations are performed on previous line).
- switch (aWhichLineBoundary) {
- case ePrevLineBegin: {
- // Fetch a previous line and move to its start (as arrow up and home keys
- // were pressed).
- if (IsEmptyLastLineOffset(aOffset))
- return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
- uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
- return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
- }
- case ePrevLineEnd: {
- if (IsEmptyLastLineOffset(aOffset))
- return aOffset - 1;
- // If offset is at first line then return 0 (first line start).
- uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
- if (tmpOffset == 0)
- return 0;
- // Otherwise move to end of previous line (as arrow up and end keys were
- // pressed).
- tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
- return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
- }
- case eThisLineBegin:
- if (IsEmptyLastLineOffset(aOffset))
- return aOffset;
- // Move to begin of the current line (as home key was pressed).
- return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
- case eThisLineEnd:
- if (IsEmptyLastLineOffset(aOffset))
- return aOffset;
- // Move to end of the current line (as end key was pressed).
- return FindOffset(aOffset, eDirNext, eSelectEndLine);
- case eNextLineBegin: {
- if (IsEmptyLastLineOffset(aOffset))
- return aOffset;
- // Move to begin of the next line if any (arrow down and home keys),
- // otherwise end of the current line (arrow down only).
- uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
- if (tmpOffset == CharacterCount())
- return tmpOffset;
- return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
- }
- case eNextLineEnd: {
- if (IsEmptyLastLineOffset(aOffset))
- return aOffset;
- // Move to next line end (as down arrow and end key were pressed).
- uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
- if (tmpOffset == CharacterCount())
- return tmpOffset;
- return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
- }
- }
- return 0;
- }
- void
- HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
- AccessibleTextBoundary aBoundaryType,
- int32_t* aStartOffset, int32_t* aEndOffset,
- nsAString& aText)
- {
- *aStartOffset = *aEndOffset = 0;
- aText.Truncate();
- index_t convertedOffset = ConvertMagicOffset(aOffset);
- if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
- NS_ERROR("Wrong in offset!");
- return;
- }
- uint32_t adjustedOffset = convertedOffset;
- if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
- adjustedOffset = AdjustCaretOffset(adjustedOffset);
- switch (aBoundaryType) {
- case nsIAccessibleText::BOUNDARY_CHAR:
- if (convertedOffset != 0)
- CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset);
- break;
- case nsIAccessibleText::BOUNDARY_WORD_START: {
- // If the offset is a word start (except text length offset) then move
- // backward to find a start offset (end offset is the given offset).
- // Otherwise move backward twice to find both start and end offsets.
- if (adjustedOffset == CharacterCount()) {
- *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
- *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
- } else {
- *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
- *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
- if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) {
- *aEndOffset = *aStartOffset;
- *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
- }
- }
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- }
- case nsIAccessibleText::BOUNDARY_WORD_END: {
- // Move word backward twice to find start and end offsets.
- *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord);
- *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- }
- case nsIAccessibleText::BOUNDARY_LINE_START:
- *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin);
- *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_LINE_END: {
- *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
- int32_t tmpOffset = *aEndOffset;
- // Adjust offset if line is wrapped.
- if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset))
- tmpOffset--;
- *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- }
- }
- }
- void
- HyperTextAccessible::TextAtOffset(int32_t aOffset,
- AccessibleTextBoundary aBoundaryType,
- int32_t* aStartOffset, int32_t* aEndOffset,
- nsAString& aText)
- {
- *aStartOffset = *aEndOffset = 0;
- aText.Truncate();
- uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
- if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
- NS_ERROR("Wrong given offset!");
- return;
- }
- switch (aBoundaryType) {
- case nsIAccessibleText::BOUNDARY_CHAR:
- // Return no char if caret is at the end of wrapped line (case of no line
- // end character). Returning a next line char is confusing for AT.
- if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine())
- *aStartOffset = *aEndOffset = adjustedOffset;
- else
- CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
- break;
- case nsIAccessibleText::BOUNDARY_WORD_START:
- if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
- adjustedOffset = AdjustCaretOffset(adjustedOffset);
- *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
- *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_WORD_END:
- // Ignore the spec and follow what WebKitGtk does because Orca expects it,
- // i.e. return a next word at word end offset of the current word
- // (WebKitGtk behavior) instead the current word (AKT spec).
- *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
- *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_LINE_START:
- if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
- adjustedOffset = AdjustCaretOffset(adjustedOffset);
- *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
- *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_LINE_END:
- if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
- adjustedOffset = AdjustCaretOffset(adjustedOffset);
- // In contrast to word end boundary we follow the spec here.
- *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
- *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- }
- }
- void
- HyperTextAccessible::TextAfterOffset(int32_t aOffset,
- AccessibleTextBoundary aBoundaryType,
- int32_t* aStartOffset, int32_t* aEndOffset,
- nsAString& aText)
- {
- *aStartOffset = *aEndOffset = 0;
- aText.Truncate();
- index_t convertedOffset = ConvertMagicOffset(aOffset);
- if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
- NS_ERROR("Wrong in offset!");
- return;
- }
- uint32_t adjustedOffset = convertedOffset;
- if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
- adjustedOffset = AdjustCaretOffset(adjustedOffset);
- switch (aBoundaryType) {
- case nsIAccessibleText::BOUNDARY_CHAR:
- // If caret is at the end of wrapped line (case of no line end character)
- // then char after the offset is a first char at next line.
- if (adjustedOffset >= CharacterCount())
- *aStartOffset = *aEndOffset = CharacterCount();
- else
- CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset);
- break;
- case nsIAccessibleText::BOUNDARY_WORD_START:
- // Move word forward twice to find start and end offsets.
- *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
- *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_WORD_END:
- // If the offset is a word end (except 0 offset) then move forward to find
- // end offset (start offset is the given offset). Otherwise move forward
- // twice to find both start and end offsets.
- if (convertedOffset == 0) {
- *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
- *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
- } else {
- *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
- *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
- if (*aStartOffset != static_cast<int32_t>(convertedOffset)) {
- *aStartOffset = *aEndOffset;
- *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
- }
- }
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_LINE_START:
- *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
- *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- case nsIAccessibleText::BOUNDARY_LINE_END:
- *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
- *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd);
- TextSubstring(*aStartOffset, *aEndOffset, aText);
- break;
- }
- }
- already_AddRefed<nsIPersistentProperties>
- HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
- int32_t* aStartOffset,
- int32_t* aEndOffset)
- {
- // 1. Get each attribute and its ranges one after another.
- // 2. As we get each new attribute, we pass the current start and end offsets
- // as in/out parameters. In other words, as attributes are collected,
- // the attribute range itself can only stay the same or get smaller.
- *aStartOffset = *aEndOffset = 0;
- index_t offset = ConvertMagicOffset(aOffset);
- if (!offset.IsValid() || offset > CharacterCount()) {
- NS_ERROR("Wrong in offset!");
- return nullptr;
- }
- nsCOMPtr<nsIPersistentProperties> attributes =
- do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
- Accessible* accAtOffset = GetChildAtOffset(offset);
- if (!accAtOffset) {
- // Offset 0 is correct offset when accessible has empty text. Include
- // default attributes if they were requested, otherwise return empty set.
- if (offset == 0) {
- if (aIncludeDefAttrs) {
- TextAttrsMgr textAttrsMgr(this);
- textAttrsMgr.GetAttributes(attributes);
- }
- return attributes.forget();
- }
- return nullptr;
- }
- int32_t accAtOffsetIdx = accAtOffset->IndexInParent();
- uint32_t startOffset = GetChildOffset(accAtOffsetIdx);
- uint32_t endOffset = GetChildOffset(accAtOffsetIdx + 1);
- int32_t offsetInAcc = offset - startOffset;
- TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset,
- accAtOffsetIdx);
- textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset);
- // Compute spelling attributes on text accessible only.
- nsIFrame *offsetFrame = accAtOffset->GetFrame();
- if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) {
- int32_t nodeOffset = 0;
- RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset);
- // Set 'misspelled' text attribute.
- GetSpellTextAttr(accAtOffset->GetNode(), nodeOffset,
- &startOffset, &endOffset, attributes);
- }
- *aStartOffset = startOffset;
- *aEndOffset = endOffset;
- return attributes.forget();
- }
- already_AddRefed<nsIPersistentProperties>
- HyperTextAccessible::DefaultTextAttributes()
- {
- nsCOMPtr<nsIPersistentProperties> attributes =
- do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
- TextAttrsMgr textAttrsMgr(this);
- textAttrsMgr.GetAttributes(attributes);
- return attributes.forget();
- }
- int32_t
- HyperTextAccessible::GetLevelInternal()
- {
- if (mContent->IsHTMLElement(nsGkAtoms::h1))
- return 1;
- if (mContent->IsHTMLElement(nsGkAtoms::h2))
- return 2;
- if (mContent->IsHTMLElement(nsGkAtoms::h3))
- return 3;
- if (mContent->IsHTMLElement(nsGkAtoms::h4))
- return 4;
- if (mContent->IsHTMLElement(nsGkAtoms::h5))
- return 5;
- if (mContent->IsHTMLElement(nsGkAtoms::h6))
- return 6;
- return AccessibleWrap::GetLevelInternal();
- }
- void
- HyperTextAccessible::SetMathMLXMLRoles(nsIPersistentProperties* aAttributes)
- {
- // Add MathML xmlroles based on the position inside the parent.
- Accessible* parent = Parent();
- if (parent) {
- switch (parent->Role()) {
- case roles::MATHML_CELL:
- case roles::MATHML_ENCLOSED:
- case roles::MATHML_ERROR:
- case roles::MATHML_MATH:
- case roles::MATHML_ROW:
- case roles::MATHML_SQUARE_ROOT:
- case roles::MATHML_STYLE:
- if (Role() == roles::MATHML_OPERATOR) {
- // This is an operator inside an <mrow> (or an inferred <mrow>).
- // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
- // XXX We should probably do something similar for MATHML_FENCED, but
- // operators do not appear in the accessible tree. See bug 1175747.
- nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame());
- if (mathMLFrame) {
- nsEmbellishData embellishData;
- mathMLFrame->GetEmbellishData(embellishData);
- if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) {
- if (!PrevSibling()) {
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- nsGkAtoms::open_fence);
- } else if (!NextSibling()) {
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- nsGkAtoms::close_fence);
- }
- }
- if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) {
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- nsGkAtoms::separator_);
- }
- }
- }
- break;
- case roles::MATHML_FRACTION:
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- IndexInParent() == 0 ?
- nsGkAtoms::numerator :
- nsGkAtoms::denominator);
- break;
- case roles::MATHML_ROOT:
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- IndexInParent() == 0 ? nsGkAtoms::base :
- nsGkAtoms::root_index);
- break;
- case roles::MATHML_SUB:
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- IndexInParent() == 0 ? nsGkAtoms::base :
- nsGkAtoms::subscript);
- break;
- case roles::MATHML_SUP:
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- IndexInParent() == 0 ? nsGkAtoms::base :
- nsGkAtoms::superscript);
- break;
- case roles::MATHML_SUB_SUP: {
- int32_t index = IndexInParent();
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- index == 0 ? nsGkAtoms::base :
- (index == 1 ? nsGkAtoms::subscript :
- nsGkAtoms::superscript));
- } break;
- case roles::MATHML_UNDER:
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- IndexInParent() == 0 ? nsGkAtoms::base :
- nsGkAtoms::underscript);
- break;
- case roles::MATHML_OVER:
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- IndexInParent() == 0 ? nsGkAtoms::base :
- nsGkAtoms::overscript);
- break;
- case roles::MATHML_UNDER_OVER: {
- int32_t index = IndexInParent();
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- index == 0 ? nsGkAtoms::base :
- (index == 1 ? nsGkAtoms::underscript :
- nsGkAtoms::overscript));
- } break;
- case roles::MATHML_MULTISCRIPTS: {
- // Get the <multiscripts> base.
- nsIContent* child;
- bool baseFound = false;
- for (child = parent->GetContent()->GetFirstChild(); child;
- child = child->GetNextSibling()) {
- if (child->IsMathMLElement()) {
- baseFound = true;
- break;
- }
- }
- if (baseFound) {
- nsIContent* content = GetContent();
- if (child == content) {
- // We are the base.
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- nsGkAtoms::base);
- } else {
- // Browse the list of scripts to find us and determine our type.
- bool postscript = true;
- bool subscript = true;
- for (child = child->GetNextSibling(); child;
- child = child->GetNextSibling()) {
- if (!child->IsMathMLElement())
- continue;
- if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) {
- postscript = false;
- subscript = true;
- continue;
- }
- if (child == content) {
- if (postscript) {
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- subscript ?
- nsGkAtoms::subscript :
- nsGkAtoms::superscript);
- } else {
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
- subscript ?
- nsGkAtoms::presubscript :
- nsGkAtoms::presuperscript);
- }
- break;
- }
- subscript = !subscript;
- }
- }
- }
- } break;
- default:
- break;
- }
- }
- }
- already_AddRefed<nsIPersistentProperties>
- HyperTextAccessible::NativeAttributes()
- {
- nsCOMPtr<nsIPersistentProperties> attributes =
- AccessibleWrap::NativeAttributes();
- // 'formatting' attribute is deprecated, 'display' attribute should be
- // instead.
- nsIFrame *frame = GetFrame();
- if (frame && frame->GetType() == nsGkAtoms::blockFrame) {
- nsAutoString unused;
- attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"),
- NS_LITERAL_STRING("block"), unused);
- }
- if (FocusMgr()->IsFocused(this)) {
- int32_t lineNumber = CaretLineNumber();
- if (lineNumber >= 1) {
- nsAutoString strLineNumber;
- strLineNumber.AppendInt(lineNumber);
- nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber);
- }
- }
- if (HasOwnContent()) {
- GetAccService()->MarkupAttributes(mContent, attributes);
- if (mContent->IsMathMLElement())
- SetMathMLXMLRoles(attributes);
- }
- return attributes.forget();
- }
- nsIAtom*
- HyperTextAccessible::LandmarkRole() const
- {
- if (!HasOwnContent())
- return nullptr;
- // For the html landmark elements we expose them like we do ARIA landmarks to
- // make AT navigation schemes "just work".
- if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
- return nsGkAtoms::navigation;
- }
- if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
- nsGkAtoms::footer)) {
- // Only map header and footer if they are not descendants of an article
- // or section tag.
- nsIContent* parent = mContent->GetParent();
- while (parent) {
- if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::section)) {
- break;
- }
- parent = parent->GetParent();
- }
- // No article or section elements found.
- if (!parent) {
- if (mContent->IsHTMLElement(nsGkAtoms::header)) {
- return nsGkAtoms::banner;
- }
- if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
- return nsGkAtoms::contentinfo;
- }
- }
- return nullptr;
- }
- if (mContent->IsHTMLElement(nsGkAtoms::aside)) {
- return nsGkAtoms::complementary;
- }
- if (mContent->IsHTMLElement(nsGkAtoms::main)) {
- return nsGkAtoms::main;
- }
- return nullptr;
- }
- int32_t
- HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
- {
- nsIFrame* hyperFrame = GetFrame();
- if (!hyperFrame)
- return -1;
- nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType,
- this);
- nsPresContext* presContext = mDoc->PresContext();
- nsPoint coordsInAppUnits =
- ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
- nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
- if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y))
- return -1; // Not found
- nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x,
- coordsInAppUnits.y - frameScreenRect.y);
- // Go through the frames to check if each one has the point.
- // When one does, add up the character offsets until we have a match
- // We have an point in an accessible child of this, now we need to add up the
- // offsets before it to what we already have
- int32_t offset = 0;
- uint32_t childCount = ChildCount();
- for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
- Accessible* childAcc = mChildren[childIdx];
- nsIFrame *primaryFrame = childAcc->GetFrame();
- NS_ENSURE_TRUE(primaryFrame, -1);
- nsIFrame *frame = primaryFrame;
- while (frame) {
- nsIContent *content = frame->GetContent();
- NS_ENSURE_TRUE(content, -1);
- nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
- nsSize frameSize = frame->GetSize();
- if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
- // Finished
- if (frame->GetType() == nsGkAtoms::textFrame) {
- nsIFrame::ContentOffsets contentOffsets =
- frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
- if (contentOffsets.IsNull() || contentOffsets.content != content) {
- return -1; // Not found
- }
- uint32_t addToOffset;
- nsresult rv = ContentToRenderedOffset(primaryFrame,
- contentOffsets.offset,
- &addToOffset);
- NS_ENSURE_SUCCESS(rv, -1);
- offset += addToOffset;
- }
- return offset;
- }
- frame = frame->GetNextContinuation();
- }
- offset += nsAccUtils::TextLength(childAcc);
- }
- return -1; // Not found
- }
- nsIntRect
- HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
- uint32_t aCoordType)
- {
- index_t startOffset = ConvertMagicOffset(aStartOffset);
- index_t endOffset = ConvertMagicOffset(aEndOffset);
- if (!startOffset.IsValid() || !endOffset.IsValid() ||
- startOffset > endOffset || endOffset > CharacterCount()) {
- NS_ERROR("Wrong in offset");
- return nsIntRect();
- }
- int32_t childIdx = GetChildIndexAtOffset(startOffset);
- if (childIdx == -1)
- return nsIntRect();
- nsIntRect bounds;
- int32_t prevOffset = GetChildOffset(childIdx);
- int32_t offset1 = startOffset - prevOffset;
- while (childIdx < static_cast<int32_t>(ChildCount())) {
- nsIFrame* frame = GetChildAt(childIdx++)->GetFrame();
- if (!frame) {
- NS_NOTREACHED("No frame for a child!");
- continue;
- }
- int32_t nextOffset = GetChildOffset(childIdx);
- if (nextOffset >= static_cast<int32_t>(endOffset)) {
- bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
- endOffset - prevOffset));
- break;
- }
- bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
- nextOffset - prevOffset));
- prevOffset = nextOffset;
- offset1 = 0;
- }
- nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this);
- return bounds;
- }
- already_AddRefed<nsIEditor>
- HyperTextAccessible::GetEditor() const
- {
- if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
- // If we're inside an editable container, then return that container's editor
- Accessible* ancestor = Parent();
- while (ancestor) {
- HyperTextAccessible* hyperText = ancestor->AsHyperText();
- if (hyperText) {
- // Recursion will stop at container doc because it has its own impl
- // of GetEditor()
- return hyperText->GetEditor();
- }
- ancestor = ancestor->Parent();
- }
- return nullptr;
- }
- nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
- nsCOMPtr<nsIEditingSession> editingSession;
- docShell->GetEditingSession(getter_AddRefs(editingSession));
- if (!editingSession)
- return nullptr; // No editing session interface
- nsCOMPtr<nsIEditor> editor;
- nsIDocument* docNode = mDoc->DocumentNode();
- editingSession->GetEditorForWindow(docNode->GetWindow(),
- getter_AddRefs(editor));
- return editor.forget();
- }
- /**
- * =================== Caret & Selection ======================
- */
- nsresult
- HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos)
- {
- // Before setting the selection range, we need to ensure that the editor
- // is initialized. (See bug 804927.)
- // Otherwise, it's possible that lazy editor initialization will override
- // the selection we set here and leave the caret at the end of the text.
- // By calling GetEditor here, we ensure that editor initialization is
- // completed before we set the selection.
- nsCOMPtr<nsIEditor> editor = GetEditor();
- bool isFocusable = InteractiveState() & states::FOCUSABLE;
- // If accessible is focusable then focus it before setting the selection to
- // neglect control's selection changes on focus if any (for example, inputs
- // that do select all on focus).
- // some input controls
- if (isFocusable)
- TakeFocus();
- dom::Selection* domSel = DOMSelection();
- NS_ENSURE_STATE(domSel);
- // Set up the selection.
- for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--)
- domSel->RemoveRange(domSel->GetRangeAt(idx));
- SetSelectionBoundsAt(0, aStartPos, aEndPos);
- // When selection is done, move the focus to the selection if accessible is
- // not focusable. That happens when selection is set within hypertext
- // accessible.
- if (isFocusable)
- return NS_OK;
- nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
- if (DOMFocusManager) {
- NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
- nsIDocument* docNode = mDoc->DocumentNode();
- NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
- nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow();
- nsCOMPtr<nsIDOMElement> result;
- DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
- nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result));
- }
- return NS_OK;
- }
- int32_t
- HyperTextAccessible::CaretOffset() const
- {
- // Not focused focusable accessible except document accessible doesn't have
- // a caret.
- if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
- (InteractiveState() & states::FOCUSABLE)) {
- return -1;
- }
- // Check cached value.
- int32_t caretOffset = -1;
- HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset);
- // Use cached value if it corresponds to this accessible.
- if (caretOffset != -1) {
- if (text == this)
- return caretOffset;
- nsINode* textNode = text->GetNode();
- // Ignore offset if cached accessible isn't a text leaf.
- if (nsCoreUtils::IsAncestorOf(GetNode(), textNode))
- return TransformOffset(text,
- textNode->IsNodeOfType(nsINode::eTEXT) ? caretOffset : 0, false);
- }
- // No caret if the focused node is not inside this DOM node and this DOM node
- // is not inside of focused node.
- FocusManager::FocusDisposition focusDisp =
- FocusMgr()->IsInOrContainsFocus(this);
- if (focusDisp == FocusManager::eNone)
- return -1;
- // Turn the focus node and offset of the selection into caret hypretext
- // offset.
- dom::Selection* domSel = DOMSelection();
- NS_ENSURE_TRUE(domSel, -1);
- nsINode* focusNode = domSel->GetFocusNode();
- uint32_t focusOffset = domSel->FocusOffset();
- // No caret if this DOM node is inside of focused node but the selection's
- // focus point is not inside of this DOM node.
- if (focusDisp == FocusManager::eContainedByFocus) {
- nsINode* resultNode =
- nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
- nsINode* thisNode = GetNode();
- if (resultNode != thisNode &&
- !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
- return -1;
- }
- return DOMPointToOffset(focusNode, focusOffset);
- }
- int32_t
- HyperTextAccessible::CaretLineNumber()
- {
- // Provide the line number for the caret, relative to the
- // currently focused node. Use a 1-based index
- RefPtr<nsFrameSelection> frameSelection = FrameSelection();
- if (!frameSelection)
- return -1;
- dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal);
- if (!domSel)
- return - 1;
- nsINode* caretNode = domSel->GetFocusNode();
- if (!caretNode || !caretNode->IsContent())
- return -1;
- nsIContent* caretContent = caretNode->AsContent();
- if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent))
- return -1;
- int32_t returnOffsetUnused;
- uint32_t caretOffset = domSel->FocusOffset();
- CaretAssociationHint hint = frameSelection->GetHint();
- nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
- hint, &returnOffsetUnused);
- NS_ENSURE_TRUE(caretFrame, -1);
- int32_t lineNumber = 1;
- nsAutoLineIterator lineIterForCaret;
- nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr;
- while (caretFrame) {
- if (hyperTextContent == caretFrame->GetContent()) {
- return lineNumber; // Must be in a single line hyper text, there is no line iterator
- }
- nsContainerFrame *parentFrame = caretFrame->GetParent();
- if (!parentFrame)
- break;
- // Add lines for the sibling frames before the caret
- nsIFrame *sibling = parentFrame->PrincipalChildList().FirstChild();
- while (sibling && sibling != caretFrame) {
- nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
- if (lineIterForSibling) {
- // For the frames before that grab all the lines
- int32_t addLines = lineIterForSibling->GetNumLines();
- lineNumber += addLines;
- }
- sibling = sibling->GetNextSibling();
- }
- // Get the line number relative to the container with lines
- if (!lineIterForCaret) { // Add the caret line just once
- lineIterForCaret = parentFrame->GetLineIterator();
- if (lineIterForCaret) {
- // Ancestor of caret
- int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame);
- lineNumber += addLines;
- }
- }
- caretFrame = parentFrame;
- }
- NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
- return lineNumber;
- }
- LayoutDeviceIntRect
- HyperTextAccessible::GetCaretRect(nsIWidget** aWidget)
- {
- *aWidget = nullptr;
- RefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret();
- NS_ENSURE_TRUE(caret, LayoutDeviceIntRect());
- bool isVisible = caret->IsVisible();
- if (!isVisible)
- return LayoutDeviceIntRect();
- nsRect rect;
- nsIFrame* frame = caret->GetGeometry(&rect);
- if (!frame || rect.IsEmpty())
- return LayoutDeviceIntRect();
- nsPoint offset;
- // Offset from widget origin to the frame origin, which includes chrome
- // on the widget.
- *aWidget = frame->GetNearestWidget(offset);
- NS_ENSURE_TRUE(*aWidget, LayoutDeviceIntRect());
- rect.MoveBy(offset);
- LayoutDeviceIntRect caretRect = LayoutDeviceIntRect::FromUnknownRect(
- rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel()));
- // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
- caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset());
- // Correct for character size, so that caret always matches the size of
- // the character. This is important for font size transitions, and is
- // necessary because the Gecko caret uses the previous character's size as
- // the user moves forward in the text by character.
- nsIntRect charRect = CharBounds(CaretOffset(),
- nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
- if (!charRect.IsEmpty()) {
- caretRect.height -= charRect.y - caretRect.y;
- caretRect.y = charRect.y;
- }
- return caretRect;
- }
- void
- HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType,
- nsTArray<nsRange*>* aRanges)
- {
- // Ignore selection if it is not visible.
- RefPtr<nsFrameSelection> frameSelection = FrameSelection();
- if (!frameSelection ||
- frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN)
- return;
- dom::Selection* domSel = frameSelection->GetSelection(aSelectionType);
- if (!domSel)
- return;
- nsCOMPtr<nsINode> startNode = GetNode();
- nsCOMPtr<nsIEditor> editor = GetEditor();
- if (editor) {
- nsCOMPtr<nsIDOMElement> editorRoot;
- editor->GetRootElement(getter_AddRefs(editorRoot));
- startNode = do_QueryInterface(editorRoot);
- }
- if (!startNode)
- return;
- uint32_t childCount = startNode->GetChildCount();
- nsresult rv = domSel->
- GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges);
- NS_ENSURE_SUCCESS_VOID(rv);
- // Remove collapsed ranges
- uint32_t numRanges = aRanges->Length();
- for (uint32_t idx = 0; idx < numRanges; idx ++) {
- if ((*aRanges)[idx]->Collapsed()) {
- aRanges->RemoveElementAt(idx);
- --numRanges;
- --idx;
- }
- }
- }
- int32_t
- HyperTextAccessible::SelectionCount()
- {
- nsTArray<nsRange*> ranges;
- GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
- return ranges.Length();
- }
- bool
- HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
- int32_t* aStartOffset,
- int32_t* aEndOffset)
- {
- *aStartOffset = *aEndOffset = 0;
- nsTArray<nsRange*> ranges;
- GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
- uint32_t rangeCount = ranges.Length();
- if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount))
- return false;
- nsRange* range = ranges[aSelectionNum];
- // Get start and end points.
- nsINode* startNode = range->GetStartParent();
- nsINode* endNode = range->GetEndParent();
- int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset();
- // Make sure start is before end, by swapping DOM points. This occurs when
- // the user selects backwards in the text.
- int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset,
- startNode, startOffset);
- if (rangeCompare < 0) {
- nsINode* tempNode = startNode;
- startNode = endNode;
- endNode = tempNode;
- int32_t tempOffset = startOffset;
- startOffset = endOffset;
- endOffset = tempOffset;
- }
- if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent))
- *aStartOffset = 0;
- else
- *aStartOffset = DOMPointToOffset(startNode, startOffset);
- if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent))
- *aEndOffset = CharacterCount();
- else
- *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
- return true;
- }
- bool
- HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
- int32_t aStartOffset,
- int32_t aEndOffset)
- {
- index_t startOffset = ConvertMagicOffset(aStartOffset);
- index_t endOffset = ConvertMagicOffset(aEndOffset);
- if (!startOffset.IsValid() || !endOffset.IsValid() ||
- startOffset > endOffset || endOffset > CharacterCount()) {
- NS_ERROR("Wrong in offset");
- return false;
- }
- dom::Selection* domSel = DOMSelection();
- if (!domSel)
- return false;
- RefPtr<nsRange> range;
- uint32_t rangeCount = domSel->RangeCount();
- if (aSelectionNum == static_cast<int32_t>(rangeCount))
- range = new nsRange(mContent);
- else
- range = domSel->GetRangeAt(aSelectionNum);
- if (!range)
- return false;
- if (!OffsetsToDOMRange(startOffset, endOffset, range))
- return false;
- // If new range was created then add it, otherwise notify selection listeners
- // that existing selection range was changed.
- if (aSelectionNum == static_cast<int32_t>(rangeCount))
- return NS_SUCCEEDED(domSel->AddRange(range));
- domSel->RemoveRange(range);
- return NS_SUCCEEDED(domSel->AddRange(range));
- }
- bool
- HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum)
- {
- dom::Selection* domSel = DOMSelection();
- if (!domSel)
- return false;
- if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(domSel->RangeCount()))
- return false;
- domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum));
- return true;
- }
- void
- HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
- uint32_t aScrollType)
- {
- RefPtr<nsRange> range = new nsRange(mContent);
- if (OffsetsToDOMRange(aStartOffset, aEndOffset, range))
- nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
- }
- void
- HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
- int32_t aEndOffset,
- uint32_t aCoordinateType,
- int32_t aX, int32_t aY)
- {
- nsIFrame *frame = GetFrame();
- if (!frame)
- return;
- nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
- this);
- RefPtr<nsRange> range = new nsRange(mContent);
- if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range))
- return;
- nsPresContext* presContext = frame->PresContext();
- nsPoint coordsInAppUnits =
- ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
- bool initialScrolled = false;
- nsIFrame *parentFrame = frame;
- while ((parentFrame = parentFrame->GetParent())) {
- nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame);
- if (scrollableFrame) {
- if (!initialScrolled) {
- // Scroll substring to the given point. Turn the point into percents
- // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
- nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
- nscoord offsetPointX = coordsInAppUnits.x - frameRect.x;
- nscoord offsetPointY = coordsInAppUnits.y - frameRect.y;
- nsSize size(parentFrame->GetSize());
- // avoid divide by zero
- size.width = size.width ? size.width : 1;
- size.height = size.height ? size.height : 1;
- int16_t hPercent = offsetPointX * 100 / size.width;
- int16_t vPercent = offsetPointY * 100 / size.height;
- nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range,
- nsIPresShell::ScrollAxis(vPercent),
- nsIPresShell::ScrollAxis(hPercent));
- if (NS_FAILED(rv))
- return;
- initialScrolled = true;
- } else {
- // Substring was scrolled to the given point already inside its closest
- // scrollable area. If there are nested scrollable areas then make
- // sure we scroll lower areas to the given point inside currently
- // traversed scrollable area.
- nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
- }
- }
- frame = parentFrame;
- }
- }
- void
- HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const
- {
- if (IsTextField()) {
- aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0,
- const_cast<HyperTextAccessible*>(this), CharacterCount());
- } else {
- aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount());
- }
- }
- void
- HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const
- {
- MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
- dom::Selection* sel = DOMSelection();
- if (!sel)
- return;
- aRanges->SetCapacity(sel->RangeCount());
- for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
- nsRange* DOMRange = sel->GetRangeAt(idx);
- HyperTextAccessible* startParent =
- nsAccUtils::GetTextContainer(DOMRange->GetStartParent());
- HyperTextAccessible* endParent =
- nsAccUtils::GetTextContainer(DOMRange->GetEndParent());
- if (!startParent || !endParent)
- continue;
- int32_t startOffset =
- startParent->DOMPointToOffset(DOMRange->GetStartParent(),
- DOMRange->StartOffset(), false);
- int32_t endOffset =
- endParent->DOMPointToOffset(DOMRange->GetEndParent(),
- DOMRange->EndOffset(), true);
- TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc,
- startParent, startOffset, endParent, endOffset);
- *(aRanges->AppendElement()) = Move(tr);
- }
- }
- void
- HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const
- {
- }
- void
- HyperTextAccessible::RangeByChild(Accessible* aChild,
- a11y::TextRange& aRange) const
- {
- 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
- HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY,
- a11y::TextRange& aRange) const
- {
- Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild);
- 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);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Accessible public
- // Accessible protected
- ENameValueFlag
- HyperTextAccessible::NativeName(nsString& aName)
- {
- // Check @alt attribute for invalid img elements.
- bool hasImgAlt = false;
- if (mContent->IsHTMLElement(nsGkAtoms::img)) {
- hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
- if (!aName.IsEmpty())
- return eNameOK;
- }
- ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
- if (!aName.IsEmpty())
- return nameFlag;
- // Get name from title attribute for HTML abbr and acronym elements making it
- // a valid name from markup. Otherwise their name isn't picked up by recursive
- // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
- if (IsAbbreviation() &&
- mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName))
- aName.CompressWhitespace();
- return hasImgAlt ? eNoNameOnPurpose : eNameOK;
- }
- void
- HyperTextAccessible::Shutdown()
- {
- mOffsets.Clear();
- AccessibleWrap::Shutdown();
- }
- bool
- HyperTextAccessible::RemoveChild(Accessible* aAccessible)
- {
- int32_t childIndex = aAccessible->IndexInParent();
- int32_t count = mOffsets.Length() - childIndex;
- if (count > 0)
- mOffsets.RemoveElementsAt(childIndex, count);
- return AccessibleWrap::RemoveChild(aAccessible);
- }
- bool
- HyperTextAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
- {
- int32_t count = mOffsets.Length() - aIndex;
- if (count > 0 ) {
- mOffsets.RemoveElementsAt(aIndex, count);
- }
- return AccessibleWrap::InsertChildAt(aIndex, aChild);
- }
- Relation
- HyperTextAccessible::RelationByType(RelationType aType)
- {
- Relation rel = Accessible::RelationByType(aType);
- switch (aType) {
- case RelationType::NODE_CHILD_OF:
- if (HasOwnContent() && mContent->IsMathMLElement()) {
- Accessible* parent = Parent();
- if (parent) {
- nsIContent* parentContent = parent->GetContent();
- if (parentContent &&
- parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
- // Add a relation pointing to the parent <mroot>.
- rel.AppendTarget(parent);
- }
- }
- }
- break;
- case RelationType::NODE_PARENT_OF:
- if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
- Accessible* base = GetChildAt(0);
- Accessible* index = GetChildAt(1);
- if (base && index) {
- // Append the <mroot> children in the order index, base.
- rel.AppendTarget(index);
- rel.AppendTarget(base);
- }
- }
- break;
- default:
- break;
- }
- return rel;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // HyperTextAccessible public static
- nsresult
- HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset,
- uint32_t* aRenderedOffset) const
- {
- if (!aFrame) {
- // Current frame not rendered -- this can happen if text is set on
- // something with display: none
- *aRenderedOffset = 0;
- return NS_OK;
- }
- if (IsTextField()) {
- *aRenderedOffset = aContentOffset;
- return NS_OK;
- }
- NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
- "Need text frame for offset conversion");
- NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
- "Call on primary frame only");
- nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
- aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
- nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
- *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
- return NS_OK;
- }
- nsresult
- HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset,
- int32_t* aContentOffset) const
- {
- if (IsTextField()) {
- *aContentOffset = aRenderedOffset;
- return NS_OK;
- }
- *aContentOffset = 0;
- NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
- NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
- "Need text frame for offset conversion");
- NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
- "Call on primary frame only");
- nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
- aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT,
- nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
- *aContentOffset = text.mOffsetWithinNodeText;
- return NS_OK;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // HyperTextAccessible public
- int32_t
- HyperTextAccessible::GetChildOffset(uint32_t aChildIndex,
- bool aInvalidateAfter) const
- {
- if (aChildIndex == 0) {
- if (aInvalidateAfter)
- mOffsets.Clear();
- return aChildIndex;
- }
- int32_t count = mOffsets.Length() - aChildIndex;
- if (count > 0) {
- if (aInvalidateAfter)
- mOffsets.RemoveElementsAt(aChildIndex, count);
- return mOffsets[aChildIndex - 1];
- }
- uint32_t lastOffset = mOffsets.IsEmpty() ?
- 0 : mOffsets[mOffsets.Length() - 1];
- while (mOffsets.Length() < aChildIndex) {
- Accessible* child = mChildren[mOffsets.Length()];
- lastOffset += nsAccUtils::TextLength(child);
- mOffsets.AppendElement(lastOffset);
- }
- return mOffsets[aChildIndex - 1];
- }
- int32_t
- HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const
- {
- uint32_t lastOffset = 0;
- const uint32_t offsetCount = mOffsets.Length();
- if (offsetCount > 0) {
- lastOffset = mOffsets[offsetCount - 1];
- if (aOffset < lastOffset) {
- size_t index;
- if (BinarySearch(mOffsets, 0, offsetCount, aOffset, &index)) {
- return (index < (offsetCount - 1)) ? index + 1 : index;
- }
- return (index == offsetCount) ? -1 : index;
- }
- }
- uint32_t childCount = ChildCount();
- while (mOffsets.Length() < childCount) {
- Accessible* child = GetChildAt(mOffsets.Length());
- lastOffset += nsAccUtils::TextLength(child);
- mOffsets.AppendElement(lastOffset);
- if (aOffset < lastOffset)
- return mOffsets.Length() - 1;
- }
- if (aOffset == lastOffset)
- return mOffsets.Length() - 1;
- return -1;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // HyperTextAccessible protected
- nsresult
- HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
- Accessible* aAccessible,
- DOMPoint* aPoint)
- {
- NS_ENSURE_ARG(aAccessible);
- if (!aFrame) {
- // If the given frame is null then set offset after the DOM node of the
- // given accessible.
- NS_ASSERTION(!aAccessible->IsDoc(),
- "Shouldn't be called on document accessible!");
- nsIContent* content = aAccessible->GetContent();
- NS_ASSERTION(content, "Shouldn't operate on defunct accessible!");
- nsIContent* parent = content->GetParent();
- aPoint->idx = parent->IndexOf(content) + 1;
- aPoint->node = parent;
- } else if (aFrame->GetType() == nsGkAtoms::textFrame) {
- nsIContent* content = aFrame->GetContent();
- NS_ENSURE_STATE(content);
- nsIFrame *primaryFrame = content->GetPrimaryFrame();
- nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
- NS_ENSURE_SUCCESS(rv, rv);
- aPoint->node = content;
- } else {
- nsIContent* content = aFrame->GetContent();
- NS_ENSURE_STATE(content);
- nsIContent* parent = content->GetParent();
- NS_ENSURE_STATE(parent);
- aPoint->idx = parent->IndexOf(content);
- aPoint->node = parent;
- }
- return NS_OK;
- }
- // HyperTextAccessible
- void
- HyperTextAccessible::GetSpellTextAttr(nsINode* aNode,
- int32_t aNodeOffset,
- uint32_t* aStartOffset,
- uint32_t* aEndOffset,
- nsIPersistentProperties* aAttributes)
- {
- RefPtr<nsFrameSelection> fs = FrameSelection();
- if (!fs)
- return;
- dom::Selection* domSel = fs->GetSelection(SelectionType::eSpellCheck);
- if (!domSel)
- return;
- int32_t rangeCount = domSel->RangeCount();
- if (rangeCount <= 0)
- return;
- uint32_t startOffset = 0, endOffset = 0;
- for (int32_t idx = 0; idx < rangeCount; idx++) {
- nsRange* range = domSel->GetRangeAt(idx);
- if (range->Collapsed())
- continue;
- // See if the point comes after the range in which case we must continue in
- // case there is another range after this one.
- nsINode* endNode = range->GetEndParent();
- int32_t endNodeOffset = range->EndOffset();
- if (nsContentUtils::ComparePoints(aNode, aNodeOffset,
- endNode, endNodeOffset) >= 0)
- continue;
- // At this point our point is either in this range or before it but after
- // the previous range. So we check to see if the range starts before the
- // point in which case the point is in the missspelled range, otherwise it
- // must be before the range and after the previous one if any.
- nsINode* startNode = range->GetStartParent();
- int32_t startNodeOffset = range->StartOffset();
- if (nsContentUtils::ComparePoints(startNode, startNodeOffset, aNode,
- aNodeOffset) <= 0) {
- startOffset = DOMPointToOffset(startNode, startNodeOffset);
- endOffset = DOMPointToOffset(endNode, endNodeOffset);
- if (startOffset > *aStartOffset)
- *aStartOffset = startOffset;
- if (endOffset < *aEndOffset)
- *aEndOffset = endOffset;
- if (aAttributes) {
- nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
- NS_LITERAL_STRING("spelling"));
- }
- return;
- }
- // This range came after the point.
- endOffset = DOMPointToOffset(startNode, startNodeOffset);
- if (idx > 0) {
- nsRange* prevRange = domSel->GetRangeAt(idx - 1);
- startOffset = DOMPointToOffset(prevRange->GetEndParent(),
- prevRange->EndOffset());
- }
- if (startOffset > *aStartOffset)
- *aStartOffset = startOffset;
- if (endOffset < *aEndOffset)
- *aEndOffset = endOffset;
- return;
- }
- // We never found a range that ended after the point, therefore we know that
- // the point is not in a range, that we do not need to compute an end offset,
- // and that we should use the end offset of the last range to compute the
- // start offset of the text attribute range.
- nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1);
- startOffset = DOMPointToOffset(prevRange->GetEndParent(),
- prevRange->EndOffset());
- if (startOffset > *aStartOffset)
- *aStartOffset = startOffset;
- }
- bool
- HyperTextAccessible::IsTextRole()
- {
- const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
- if (roleMapEntry &&
- (roleMapEntry->role == roles::GRAPHIC ||
- roleMapEntry->role == roles::IMAGE_MAP ||
- roleMapEntry->role == roles::SLIDER ||
- roleMapEntry->role == roles::PROGRESSBAR ||
- roleMapEntry->role == roles::SEPARATOR))
- return false;
- return true;
- }
|