HyperTextAccessible.cpp 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "HyperTextAccessible-inl.h"
  6. #include "Accessible-inl.h"
  7. #include "nsAccessibilityService.h"
  8. #include "nsIAccessibleTypes.h"
  9. #include "DocAccessible.h"
  10. #include "HTMLListAccessible.h"
  11. #include "Relation.h"
  12. #include "Role.h"
  13. #include "States.h"
  14. #include "TextAttrs.h"
  15. #include "TextRange.h"
  16. #include "TreeWalker.h"
  17. #include "nsCaret.h"
  18. #include "nsContentUtils.h"
  19. #include "nsFocusManager.h"
  20. #include "nsIDOMRange.h"
  21. #include "nsIEditingSession.h"
  22. #include "nsContainerFrame.h"
  23. #include "nsFrameSelection.h"
  24. #include "nsILineIterator.h"
  25. #include "nsIInterfaceRequestorUtils.h"
  26. #include "nsIPersistentProperties2.h"
  27. #include "nsIScrollableFrame.h"
  28. #include "nsIServiceManager.h"
  29. #include "nsITextControlElement.h"
  30. #include "nsIMathMLFrame.h"
  31. #include "nsTextFragment.h"
  32. #include "mozilla/BinarySearch.h"
  33. #include "mozilla/dom/Element.h"
  34. #include "mozilla/EventStates.h"
  35. #include "mozilla/dom/Selection.h"
  36. #include "mozilla/MathAlgorithms.h"
  37. #include "gfxSkipChars.h"
  38. #include <algorithm>
  39. using namespace mozilla;
  40. using namespace mozilla::a11y;
  41. ////////////////////////////////////////////////////////////////////////////////
  42. // HyperTextAccessible
  43. ////////////////////////////////////////////////////////////////////////////////
  44. HyperTextAccessible::
  45. HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) :
  46. AccessibleWrap(aNode, aDoc)
  47. {
  48. mType = eHyperTextType;
  49. mGenericTypes |= eHyperText;
  50. }
  51. NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessible, Accessible)
  52. role
  53. HyperTextAccessible::NativeRole()
  54. {
  55. a11y::role r = GetAccService()->MarkupRole(mContent);
  56. if (r != roles::NOTHING)
  57. return r;
  58. nsIFrame* frame = GetFrame();
  59. if (frame && frame->GetType() == nsGkAtoms::inlineFrame)
  60. return roles::TEXT;
  61. return roles::TEXT_CONTAINER;
  62. }
  63. uint64_t
  64. HyperTextAccessible::NativeState()
  65. {
  66. uint64_t states = AccessibleWrap::NativeState();
  67. if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
  68. states |= states::EDITABLE;
  69. } else if (mContent->IsHTMLElement(nsGkAtoms::article)) {
  70. // We want <article> to behave like a document in terms of readonly state.
  71. states |= states::READONLY;
  72. }
  73. if (HasChildren())
  74. states |= states::SELECTABLE_TEXT;
  75. return states;
  76. }
  77. nsIntRect
  78. HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame,
  79. uint32_t aStartRenderedOffset,
  80. uint32_t aEndRenderedOffset)
  81. {
  82. nsPresContext* presContext = mDoc->PresContext();
  83. if (aFrame->GetType() != nsGkAtoms::textFrame) {
  84. return aFrame->GetScreenRectInAppUnits().
  85. ToNearestPixels(presContext->AppUnitsPerDevPixel());
  86. }
  87. // Substring must be entirely within the same text node.
  88. int32_t startContentOffset, endContentOffset;
  89. nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset);
  90. NS_ENSURE_SUCCESS(rv, nsIntRect());
  91. rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
  92. NS_ENSURE_SUCCESS(rv, nsIntRect());
  93. nsIFrame *frame;
  94. int32_t startContentOffsetInFrame;
  95. // Get the right frame continuation -- not really a child, but a sibling of
  96. // the primary frame passed in
  97. rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false,
  98. &startContentOffsetInFrame, &frame);
  99. NS_ENSURE_SUCCESS(rv, nsIntRect());
  100. nsRect screenRect;
  101. while (frame && startContentOffset < endContentOffset) {
  102. // Start with this frame's screen rect, which we will shrink based on
  103. // the substring we care about within it. We will then add that frame to
  104. // the total screenRect we are returning.
  105. nsRect frameScreenRect = frame->GetScreenRectInAppUnits();
  106. // Get the length of the substring in this frame that we want the bounds for
  107. int32_t startFrameTextOffset, endFrameTextOffset;
  108. frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
  109. int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
  110. int32_t seekLength = endContentOffset - startContentOffset;
  111. int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength);
  112. // Add the point where the string starts to the frameScreenRect
  113. nsPoint frameTextStartPoint;
  114. rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
  115. NS_ENSURE_SUCCESS(rv, nsIntRect());
  116. // Use the point for the end offset to calculate the width
  117. nsPoint frameTextEndPoint;
  118. rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
  119. NS_ENSURE_SUCCESS(rv, nsIntRect());
  120. frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x);
  121. frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x);
  122. screenRect.UnionRect(frameScreenRect, screenRect);
  123. // Get ready to loop back for next frame continuation
  124. startContentOffset += frameSubStringLength;
  125. startContentOffsetInFrame = 0;
  126. frame = frame->GetNextContinuation();
  127. }
  128. return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
  129. }
  130. void
  131. HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
  132. nsAString& aText)
  133. {
  134. aText.Truncate();
  135. index_t startOffset = ConvertMagicOffset(aStartOffset);
  136. index_t endOffset = ConvertMagicOffset(aEndOffset);
  137. if (!startOffset.IsValid() || !endOffset.IsValid() ||
  138. startOffset > endOffset || endOffset > CharacterCount()) {
  139. NS_ERROR("Wrong in offset");
  140. return;
  141. }
  142. int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
  143. if (startChildIdx == -1)
  144. return;
  145. int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
  146. if (endChildIdx == -1)
  147. return;
  148. if (startChildIdx == endChildIdx) {
  149. int32_t childOffset = GetChildOffset(startChildIdx);
  150. if (childOffset == -1)
  151. return;
  152. Accessible* child = GetChildAt(startChildIdx);
  153. child->AppendTextTo(aText, startOffset - childOffset,
  154. endOffset - startOffset);
  155. return;
  156. }
  157. int32_t startChildOffset = GetChildOffset(startChildIdx);
  158. if (startChildOffset == -1)
  159. return;
  160. Accessible* startChild = GetChildAt(startChildIdx);
  161. startChild->AppendTextTo(aText, startOffset - startChildOffset);
  162. for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) {
  163. Accessible* child = GetChildAt(childIdx);
  164. child->AppendTextTo(aText);
  165. }
  166. int32_t endChildOffset = GetChildOffset(endChildIdx);
  167. if (endChildOffset == -1)
  168. return;
  169. Accessible* endChild = GetChildAt(endChildIdx);
  170. endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
  171. }
  172. uint32_t
  173. HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
  174. bool aIsEndOffset) const
  175. {
  176. if (!aNode)
  177. return 0;
  178. uint32_t offset = 0;
  179. nsINode* findNode = nullptr;
  180. if (aNodeOffset == -1) {
  181. findNode = aNode;
  182. } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
  183. // For text nodes, aNodeOffset comes in as a character offset
  184. // Text offset will be added at the end, if we find the offset in this hypertext
  185. // We want the "skipped" offset into the text (rendered text without the extra whitespace)
  186. nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
  187. NS_ENSURE_TRUE(frame, 0);
  188. nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
  189. NS_ENSURE_SUCCESS(rv, 0);
  190. findNode = aNode;
  191. } else {
  192. // findNode could be null if aNodeOffset == # of child nodes, which means
  193. // one of two things:
  194. // 1) there are no children, and the passed-in node is not mContent -- use
  195. // parentContent for the node to find
  196. // 2) there are no children and the passed-in node is mContent, which means
  197. // we're an empty nsIAccessibleText
  198. // 3) there are children and we're at the end of the children
  199. findNode = aNode->GetChildAt(aNodeOffset);
  200. if (!findNode) {
  201. if (aNodeOffset == 0) {
  202. if (aNode == GetNode()) {
  203. // Case #1: this accessible has no children and thus has empty text,
  204. // we can only be at hypertext offset 0.
  205. return 0;
  206. }
  207. // Case #2: there are no children, we're at this node.
  208. findNode = aNode;
  209. } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) {
  210. // Case #3: we're after the last child, get next node to this one.
  211. for (nsINode* tmpNode = aNode;
  212. !findNode && tmpNode && tmpNode != mContent;
  213. tmpNode = tmpNode->GetParent()) {
  214. findNode = tmpNode->GetNextSibling();
  215. }
  216. }
  217. }
  218. }
  219. // Get accessible for this findNode, or if that node isn't accessible, use the
  220. // accessible for the next DOM node which has one (based on forward depth first search)
  221. Accessible* descendant = nullptr;
  222. if (findNode) {
  223. nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode));
  224. if (findContent && findContent->IsHTMLElement() &&
  225. findContent->NodeInfo()->Equals(nsGkAtoms::br) &&
  226. findContent->AttrValueIs(kNameSpaceID_None,
  227. nsGkAtoms::mozeditorbogusnode,
  228. nsGkAtoms::_true,
  229. eIgnoreCase)) {
  230. // This <br> is the hacky "bogus node" used when there is no text in a control
  231. return 0;
  232. }
  233. descendant = mDoc->GetAccessible(findNode);
  234. if (!descendant && findNode->IsContent()) {
  235. Accessible* container = mDoc->GetContainerAccessible(findNode);
  236. if (container) {
  237. TreeWalker walker(container, findNode->AsContent(),
  238. TreeWalker::eWalkContextTree);
  239. descendant = walker.Next();
  240. if (!descendant)
  241. descendant = container;
  242. }
  243. }
  244. }
  245. return TransformOffset(descendant, offset, aIsEndOffset);
  246. }
  247. uint32_t
  248. HyperTextAccessible::TransformOffset(Accessible* aDescendant,
  249. uint32_t aOffset, bool aIsEndOffset) const
  250. {
  251. // From the descendant, go up and get the immediate child of this hypertext.
  252. uint32_t offset = aOffset;
  253. Accessible* descendant = aDescendant;
  254. while (descendant) {
  255. Accessible* parent = descendant->Parent();
  256. if (parent == this)
  257. return GetChildOffset(descendant) + offset;
  258. // This offset no longer applies because the passed-in text object is not
  259. // a child of the hypertext. This happens when there are nested hypertexts,
  260. // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
  261. // to make it relative the hypertext.
  262. // If the end offset is not supposed to be inclusive and the original point
  263. // is not at 0 offset then the returned offset should be after an embedded
  264. // character the original point belongs to.
  265. if (aIsEndOffset)
  266. offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
  267. else
  268. offset = 0;
  269. descendant = parent;
  270. }
  271. // If the given a11y point cannot be mapped into offset relative this hypertext
  272. // offset then return length as fallback value.
  273. return CharacterCount();
  274. }
  275. /**
  276. * GetElementAsContentOf() returns a content representing an element which is
  277. * or includes aNode.
  278. *
  279. * XXX This method is enough to retrieve ::before or ::after pseudo element.
  280. * So, if you want to use this for other purpose, you might need to check
  281. * ancestors too.
  282. */
  283. static nsIContent* GetElementAsContentOf(nsINode* aNode)
  284. {
  285. if (aNode->IsElement()) {
  286. return aNode->AsContent();
  287. }
  288. nsIContent* parent = aNode->GetParent();
  289. return parent && parent->IsElement() ? parent : nullptr;
  290. }
  291. bool
  292. HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
  293. nsRange* aRange)
  294. {
  295. DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
  296. if (!startPoint.node)
  297. return false;
  298. // HyperTextAccessible manages pseudo elements generated by ::before or
  299. // ::after. However, contents of them are not in the DOM tree normally.
  300. // Therefore, they are not selectable and editable. So, when this creates
  301. // a DOM range, it should not start from nor end in any pseudo contents.
  302. nsIContent* container = GetElementAsContentOf(startPoint.node);
  303. DOMPoint startPointForDOMRange =
  304. ClosestNotGeneratedDOMPoint(startPoint, container);
  305. aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
  306. // If the caller wants collapsed range, let's collapse the range to its start.
  307. if (aStartOffset == aEndOffset) {
  308. aRange->Collapse(true);
  309. return true;
  310. }
  311. DOMPoint endPoint = OffsetToDOMPoint(aEndOffset);
  312. if (!endPoint.node)
  313. return false;
  314. if (startPoint.node != endPoint.node) {
  315. container = GetElementAsContentOf(endPoint.node);
  316. }
  317. DOMPoint endPointForDOMRange =
  318. ClosestNotGeneratedDOMPoint(endPoint, container);
  319. aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
  320. return true;
  321. }
  322. DOMPoint
  323. HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset)
  324. {
  325. // 0 offset is valid even if no children. In this case the associated editor
  326. // is empty so return a DOM point for editor root element.
  327. if (aOffset == 0) {
  328. nsCOMPtr<nsIEditor> editor = GetEditor();
  329. if (editor) {
  330. bool isEmpty = false;
  331. editor->GetDocumentIsEmpty(&isEmpty);
  332. if (isEmpty) {
  333. nsCOMPtr<nsIDOMElement> editorRootElm;
  334. editor->GetRootElement(getter_AddRefs(editorRootElm));
  335. nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
  336. return DOMPoint(editorRoot, 0);
  337. }
  338. }
  339. }
  340. int32_t childIdx = GetChildIndexAtOffset(aOffset);
  341. if (childIdx == -1)
  342. return DOMPoint();
  343. Accessible* child = GetChildAt(childIdx);
  344. int32_t innerOffset = aOffset - GetChildOffset(childIdx);
  345. // A text leaf case.
  346. if (child->IsTextLeaf()) {
  347. // The point is inside the text node. This is always true for any text leaf
  348. // except a last child one. See assertion below.
  349. if (aOffset < GetChildOffset(childIdx + 1)) {
  350. nsIContent* content = child->GetContent();
  351. int32_t idx = 0;
  352. if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
  353. innerOffset, &idx)))
  354. return DOMPoint();
  355. return DOMPoint(content, idx);
  356. }
  357. // Set the DOM point right after the text node.
  358. MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
  359. innerOffset = 1;
  360. }
  361. // Case of embedded object. The point is either before or after the element.
  362. NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
  363. nsINode* node = child->GetNode();
  364. nsINode* parentNode = node->GetParentNode();
  365. return parentNode ?
  366. DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
  367. DOMPoint();
  368. }
  369. DOMPoint
  370. HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
  371. nsIContent* aElementContent)
  372. {
  373. MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
  374. // ::before pseudo element
  375. if (aElementContent &&
  376. aElementContent->IsGeneratedContentContainerForBefore()) {
  377. MOZ_ASSERT(aElementContent->GetParent(),
  378. "::before must have parent element");
  379. // The first child of its parent (i.e., immediately after the ::before) is
  380. // good point for a DOM range.
  381. return DOMPoint(aElementContent->GetParent(), 0);
  382. }
  383. // ::after pseudo element
  384. if (aElementContent &&
  385. aElementContent->IsGeneratedContentContainerForAfter()) {
  386. MOZ_ASSERT(aElementContent->GetParent(),
  387. "::after must have parent element");
  388. // The end of its parent (i.e., immediately before the ::after) is good
  389. // point for a DOM range.
  390. return DOMPoint(aElementContent->GetParent(),
  391. aElementContent->GetParent()->GetChildCount());
  392. }
  393. return aDOMPoint;
  394. }
  395. uint32_t
  396. HyperTextAccessible::FindOffset(uint32_t aOffset, nsDirection aDirection,
  397. nsSelectionAmount aAmount,
  398. EWordMovementType aWordMovementType)
  399. {
  400. NS_ASSERTION(aDirection == eDirPrevious || aAmount != eSelectBeginLine,
  401. "eSelectBeginLine should only be used with eDirPrevious");
  402. // Find a leaf accessible frame to start with. PeekOffset wants this.
  403. HyperTextAccessible* text = this;
  404. Accessible* child = nullptr;
  405. int32_t innerOffset = aOffset;
  406. do {
  407. int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
  408. // We can have an empty text leaf as our only child. Since empty text
  409. // leaves are not accessible we then have no children, but 0 is a valid
  410. // innerOffset.
  411. if (childIdx == -1) {
  412. NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?");
  413. return DOMPointToOffset(text->GetNode(), 0, aDirection == eDirNext);
  414. }
  415. child = text->GetChildAt(childIdx);
  416. // HTML list items may need special processing because PeekOffset doesn't
  417. // work with list bullets.
  418. if (text->IsHTMLListItem()) {
  419. HTMLLIAccessible* li = text->AsHTMLListItem();
  420. if (child == li->Bullet()) {
  421. // XXX: the logic is broken for multichar bullets in moving by
  422. // char/cluster/word cases.
  423. if (text != this) {
  424. return aDirection == eDirPrevious ?
  425. TransformOffset(text, 0, false) :
  426. TransformOffset(text, 1, true);
  427. }
  428. if (aDirection == eDirPrevious)
  429. return 0;
  430. uint32_t nextOffset = GetChildOffset(1);
  431. if (nextOffset == 0)
  432. return 0;
  433. switch (aAmount) {
  434. case eSelectLine:
  435. case eSelectEndLine:
  436. // Ask a text leaf next (if not empty) to the bullet for an offset
  437. // since list item may be multiline.
  438. return nextOffset < CharacterCount() ?
  439. FindOffset(nextOffset, aDirection, aAmount, aWordMovementType) :
  440. nextOffset;
  441. default:
  442. return nextOffset;
  443. }
  444. }
  445. }
  446. innerOffset -= text->GetChildOffset(childIdx);
  447. text = child->AsHyperText();
  448. } while (text);
  449. nsIFrame* childFrame = child->GetFrame();
  450. if (!childFrame) {
  451. NS_ERROR("No child frame");
  452. return 0;
  453. }
  454. int32_t innerContentOffset = innerOffset;
  455. if (child->IsTextLeaf()) {
  456. NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!");
  457. RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
  458. }
  459. nsIFrame* frameAtOffset = childFrame;
  460. int32_t unusedOffsetInFrame = 0;
  461. childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
  462. &unusedOffsetInFrame,
  463. &frameAtOffset);
  464. const bool kIsJumpLinesOk = true; // okay to jump lines
  465. const bool kIsScrollViewAStop = false; // do not stop at scroll views
  466. const bool kIsKeyboardSelect = true; // is keyboard selection
  467. const bool kIsVisualBidi = false; // use visual order for bidi text
  468. nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset,
  469. nsPoint(0, 0), kIsJumpLinesOk, kIsScrollViewAStop,
  470. kIsKeyboardSelect, kIsVisualBidi,
  471. false, aWordMovementType);
  472. nsresult rv = frameAtOffset->PeekOffset(&pos);
  473. // PeekOffset fails on last/first lines of the text in certain cases.
  474. if (NS_FAILED(rv) && aAmount == eSelectLine) {
  475. pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
  476. frameAtOffset->PeekOffset(&pos);
  477. }
  478. if (!pos.mResultContent) {
  479. NS_ERROR("No result content!");
  480. return 0;
  481. }
  482. // Turn the resulting DOM point into an offset.
  483. uint32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
  484. pos.mContentOffset,
  485. aDirection == eDirNext);
  486. if (aDirection == eDirPrevious) {
  487. // If we reached the end during search, this means we didn't find the DOM point
  488. // and we're actually at the start of the paragraph
  489. if (hyperTextOffset == CharacterCount())
  490. return 0;
  491. // PeekOffset stops right before bullet so return 0 to workaround it.
  492. if (IsHTMLListItem() && aAmount == eSelectBeginLine &&
  493. hyperTextOffset > 0) {
  494. Accessible* prevOffsetChild = GetChildAtOffset(hyperTextOffset - 1);
  495. if (prevOffsetChild == AsHTMLListItem()->Bullet())
  496. return 0;
  497. }
  498. }
  499. return hyperTextOffset;
  500. }
  501. uint32_t
  502. HyperTextAccessible::FindLineBoundary(uint32_t aOffset,
  503. EWhichLineBoundary aWhichLineBoundary)
  504. {
  505. // Note: empty last line doesn't have own frame (a previous line contains '\n'
  506. // character instead) thus when it makes a difference we need to process this
  507. // case separately (otherwise operations are performed on previous line).
  508. switch (aWhichLineBoundary) {
  509. case ePrevLineBegin: {
  510. // Fetch a previous line and move to its start (as arrow up and home keys
  511. // were pressed).
  512. if (IsEmptyLastLineOffset(aOffset))
  513. return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
  514. uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
  515. return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
  516. }
  517. case ePrevLineEnd: {
  518. if (IsEmptyLastLineOffset(aOffset))
  519. return aOffset - 1;
  520. // If offset is at first line then return 0 (first line start).
  521. uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
  522. if (tmpOffset == 0)
  523. return 0;
  524. // Otherwise move to end of previous line (as arrow up and end keys were
  525. // pressed).
  526. tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
  527. return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
  528. }
  529. case eThisLineBegin:
  530. if (IsEmptyLastLineOffset(aOffset))
  531. return aOffset;
  532. // Move to begin of the current line (as home key was pressed).
  533. return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
  534. case eThisLineEnd:
  535. if (IsEmptyLastLineOffset(aOffset))
  536. return aOffset;
  537. // Move to end of the current line (as end key was pressed).
  538. return FindOffset(aOffset, eDirNext, eSelectEndLine);
  539. case eNextLineBegin: {
  540. if (IsEmptyLastLineOffset(aOffset))
  541. return aOffset;
  542. // Move to begin of the next line if any (arrow down and home keys),
  543. // otherwise end of the current line (arrow down only).
  544. uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
  545. if (tmpOffset == CharacterCount())
  546. return tmpOffset;
  547. return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
  548. }
  549. case eNextLineEnd: {
  550. if (IsEmptyLastLineOffset(aOffset))
  551. return aOffset;
  552. // Move to next line end (as down arrow and end key were pressed).
  553. uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
  554. if (tmpOffset == CharacterCount())
  555. return tmpOffset;
  556. return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
  557. }
  558. }
  559. return 0;
  560. }
  561. void
  562. HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
  563. AccessibleTextBoundary aBoundaryType,
  564. int32_t* aStartOffset, int32_t* aEndOffset,
  565. nsAString& aText)
  566. {
  567. *aStartOffset = *aEndOffset = 0;
  568. aText.Truncate();
  569. index_t convertedOffset = ConvertMagicOffset(aOffset);
  570. if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
  571. NS_ERROR("Wrong in offset!");
  572. return;
  573. }
  574. uint32_t adjustedOffset = convertedOffset;
  575. if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
  576. adjustedOffset = AdjustCaretOffset(adjustedOffset);
  577. switch (aBoundaryType) {
  578. case nsIAccessibleText::BOUNDARY_CHAR:
  579. if (convertedOffset != 0)
  580. CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset);
  581. break;
  582. case nsIAccessibleText::BOUNDARY_WORD_START: {
  583. // If the offset is a word start (except text length offset) then move
  584. // backward to find a start offset (end offset is the given offset).
  585. // Otherwise move backward twice to find both start and end offsets.
  586. if (adjustedOffset == CharacterCount()) {
  587. *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
  588. *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
  589. } else {
  590. *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
  591. *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
  592. if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) {
  593. *aEndOffset = *aStartOffset;
  594. *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
  595. }
  596. }
  597. TextSubstring(*aStartOffset, *aEndOffset, aText);
  598. break;
  599. }
  600. case nsIAccessibleText::BOUNDARY_WORD_END: {
  601. // Move word backward twice to find start and end offsets.
  602. *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord);
  603. *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
  604. TextSubstring(*aStartOffset, *aEndOffset, aText);
  605. break;
  606. }
  607. case nsIAccessibleText::BOUNDARY_LINE_START:
  608. *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin);
  609. *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
  610. TextSubstring(*aStartOffset, *aEndOffset, aText);
  611. break;
  612. case nsIAccessibleText::BOUNDARY_LINE_END: {
  613. *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
  614. int32_t tmpOffset = *aEndOffset;
  615. // Adjust offset if line is wrapped.
  616. if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset))
  617. tmpOffset--;
  618. *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd);
  619. TextSubstring(*aStartOffset, *aEndOffset, aText);
  620. break;
  621. }
  622. }
  623. }
  624. void
  625. HyperTextAccessible::TextAtOffset(int32_t aOffset,
  626. AccessibleTextBoundary aBoundaryType,
  627. int32_t* aStartOffset, int32_t* aEndOffset,
  628. nsAString& aText)
  629. {
  630. *aStartOffset = *aEndOffset = 0;
  631. aText.Truncate();
  632. uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
  633. if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
  634. NS_ERROR("Wrong given offset!");
  635. return;
  636. }
  637. switch (aBoundaryType) {
  638. case nsIAccessibleText::BOUNDARY_CHAR:
  639. // Return no char if caret is at the end of wrapped line (case of no line
  640. // end character). Returning a next line char is confusing for AT.
  641. if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine())
  642. *aStartOffset = *aEndOffset = adjustedOffset;
  643. else
  644. CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
  645. break;
  646. case nsIAccessibleText::BOUNDARY_WORD_START:
  647. if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
  648. adjustedOffset = AdjustCaretOffset(adjustedOffset);
  649. *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
  650. *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
  651. TextSubstring(*aStartOffset, *aEndOffset, aText);
  652. break;
  653. case nsIAccessibleText::BOUNDARY_WORD_END:
  654. // Ignore the spec and follow what WebKitGtk does because Orca expects it,
  655. // i.e. return a next word at word end offset of the current word
  656. // (WebKitGtk behavior) instead the current word (AKT spec).
  657. *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
  658. *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
  659. TextSubstring(*aStartOffset, *aEndOffset, aText);
  660. break;
  661. case nsIAccessibleText::BOUNDARY_LINE_START:
  662. if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
  663. adjustedOffset = AdjustCaretOffset(adjustedOffset);
  664. *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
  665. *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
  666. TextSubstring(*aStartOffset, *aEndOffset, aText);
  667. break;
  668. case nsIAccessibleText::BOUNDARY_LINE_END:
  669. if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
  670. adjustedOffset = AdjustCaretOffset(adjustedOffset);
  671. // In contrast to word end boundary we follow the spec here.
  672. *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
  673. *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
  674. TextSubstring(*aStartOffset, *aEndOffset, aText);
  675. break;
  676. }
  677. }
  678. void
  679. HyperTextAccessible::TextAfterOffset(int32_t aOffset,
  680. AccessibleTextBoundary aBoundaryType,
  681. int32_t* aStartOffset, int32_t* aEndOffset,
  682. nsAString& aText)
  683. {
  684. *aStartOffset = *aEndOffset = 0;
  685. aText.Truncate();
  686. index_t convertedOffset = ConvertMagicOffset(aOffset);
  687. if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
  688. NS_ERROR("Wrong in offset!");
  689. return;
  690. }
  691. uint32_t adjustedOffset = convertedOffset;
  692. if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
  693. adjustedOffset = AdjustCaretOffset(adjustedOffset);
  694. switch (aBoundaryType) {
  695. case nsIAccessibleText::BOUNDARY_CHAR:
  696. // If caret is at the end of wrapped line (case of no line end character)
  697. // then char after the offset is a first char at next line.
  698. if (adjustedOffset >= CharacterCount())
  699. *aStartOffset = *aEndOffset = CharacterCount();
  700. else
  701. CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset);
  702. break;
  703. case nsIAccessibleText::BOUNDARY_WORD_START:
  704. // Move word forward twice to find start and end offsets.
  705. *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
  706. *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
  707. TextSubstring(*aStartOffset, *aEndOffset, aText);
  708. break;
  709. case nsIAccessibleText::BOUNDARY_WORD_END:
  710. // If the offset is a word end (except 0 offset) then move forward to find
  711. // end offset (start offset is the given offset). Otherwise move forward
  712. // twice to find both start and end offsets.
  713. if (convertedOffset == 0) {
  714. *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
  715. *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
  716. } else {
  717. *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
  718. *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
  719. if (*aStartOffset != static_cast<int32_t>(convertedOffset)) {
  720. *aStartOffset = *aEndOffset;
  721. *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
  722. }
  723. }
  724. TextSubstring(*aStartOffset, *aEndOffset, aText);
  725. break;
  726. case nsIAccessibleText::BOUNDARY_LINE_START:
  727. *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
  728. *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
  729. TextSubstring(*aStartOffset, *aEndOffset, aText);
  730. break;
  731. case nsIAccessibleText::BOUNDARY_LINE_END:
  732. *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
  733. *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd);
  734. TextSubstring(*aStartOffset, *aEndOffset, aText);
  735. break;
  736. }
  737. }
  738. already_AddRefed<nsIPersistentProperties>
  739. HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
  740. int32_t* aStartOffset,
  741. int32_t* aEndOffset)
  742. {
  743. // 1. Get each attribute and its ranges one after another.
  744. // 2. As we get each new attribute, we pass the current start and end offsets
  745. // as in/out parameters. In other words, as attributes are collected,
  746. // the attribute range itself can only stay the same or get smaller.
  747. *aStartOffset = *aEndOffset = 0;
  748. index_t offset = ConvertMagicOffset(aOffset);
  749. if (!offset.IsValid() || offset > CharacterCount()) {
  750. NS_ERROR("Wrong in offset!");
  751. return nullptr;
  752. }
  753. nsCOMPtr<nsIPersistentProperties> attributes =
  754. do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
  755. Accessible* accAtOffset = GetChildAtOffset(offset);
  756. if (!accAtOffset) {
  757. // Offset 0 is correct offset when accessible has empty text. Include
  758. // default attributes if they were requested, otherwise return empty set.
  759. if (offset == 0) {
  760. if (aIncludeDefAttrs) {
  761. TextAttrsMgr textAttrsMgr(this);
  762. textAttrsMgr.GetAttributes(attributes);
  763. }
  764. return attributes.forget();
  765. }
  766. return nullptr;
  767. }
  768. int32_t accAtOffsetIdx = accAtOffset->IndexInParent();
  769. uint32_t startOffset = GetChildOffset(accAtOffsetIdx);
  770. uint32_t endOffset = GetChildOffset(accAtOffsetIdx + 1);
  771. int32_t offsetInAcc = offset - startOffset;
  772. TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset,
  773. accAtOffsetIdx);
  774. textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset);
  775. // Compute spelling attributes on text accessible only.
  776. nsIFrame *offsetFrame = accAtOffset->GetFrame();
  777. if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) {
  778. int32_t nodeOffset = 0;
  779. RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset);
  780. // Set 'misspelled' text attribute.
  781. GetSpellTextAttr(accAtOffset->GetNode(), nodeOffset,
  782. &startOffset, &endOffset, attributes);
  783. }
  784. *aStartOffset = startOffset;
  785. *aEndOffset = endOffset;
  786. return attributes.forget();
  787. }
  788. already_AddRefed<nsIPersistentProperties>
  789. HyperTextAccessible::DefaultTextAttributes()
  790. {
  791. nsCOMPtr<nsIPersistentProperties> attributes =
  792. do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
  793. TextAttrsMgr textAttrsMgr(this);
  794. textAttrsMgr.GetAttributes(attributes);
  795. return attributes.forget();
  796. }
  797. int32_t
  798. HyperTextAccessible::GetLevelInternal()
  799. {
  800. if (mContent->IsHTMLElement(nsGkAtoms::h1))
  801. return 1;
  802. if (mContent->IsHTMLElement(nsGkAtoms::h2))
  803. return 2;
  804. if (mContent->IsHTMLElement(nsGkAtoms::h3))
  805. return 3;
  806. if (mContent->IsHTMLElement(nsGkAtoms::h4))
  807. return 4;
  808. if (mContent->IsHTMLElement(nsGkAtoms::h5))
  809. return 5;
  810. if (mContent->IsHTMLElement(nsGkAtoms::h6))
  811. return 6;
  812. return AccessibleWrap::GetLevelInternal();
  813. }
  814. void
  815. HyperTextAccessible::SetMathMLXMLRoles(nsIPersistentProperties* aAttributes)
  816. {
  817. // Add MathML xmlroles based on the position inside the parent.
  818. Accessible* parent = Parent();
  819. if (parent) {
  820. switch (parent->Role()) {
  821. case roles::MATHML_CELL:
  822. case roles::MATHML_ENCLOSED:
  823. case roles::MATHML_ERROR:
  824. case roles::MATHML_MATH:
  825. case roles::MATHML_ROW:
  826. case roles::MATHML_SQUARE_ROOT:
  827. case roles::MATHML_STYLE:
  828. if (Role() == roles::MATHML_OPERATOR) {
  829. // This is an operator inside an <mrow> (or an inferred <mrow>).
  830. // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
  831. // XXX We should probably do something similar for MATHML_FENCED, but
  832. // operators do not appear in the accessible tree. See bug 1175747.
  833. nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame());
  834. if (mathMLFrame) {
  835. nsEmbellishData embellishData;
  836. mathMLFrame->GetEmbellishData(embellishData);
  837. if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) {
  838. if (!PrevSibling()) {
  839. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  840. nsGkAtoms::open_fence);
  841. } else if (!NextSibling()) {
  842. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  843. nsGkAtoms::close_fence);
  844. }
  845. }
  846. if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) {
  847. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  848. nsGkAtoms::separator_);
  849. }
  850. }
  851. }
  852. break;
  853. case roles::MATHML_FRACTION:
  854. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  855. IndexInParent() == 0 ?
  856. nsGkAtoms::numerator :
  857. nsGkAtoms::denominator);
  858. break;
  859. case roles::MATHML_ROOT:
  860. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  861. IndexInParent() == 0 ? nsGkAtoms::base :
  862. nsGkAtoms::root_index);
  863. break;
  864. case roles::MATHML_SUB:
  865. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  866. IndexInParent() == 0 ? nsGkAtoms::base :
  867. nsGkAtoms::subscript);
  868. break;
  869. case roles::MATHML_SUP:
  870. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  871. IndexInParent() == 0 ? nsGkAtoms::base :
  872. nsGkAtoms::superscript);
  873. break;
  874. case roles::MATHML_SUB_SUP: {
  875. int32_t index = IndexInParent();
  876. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  877. index == 0 ? nsGkAtoms::base :
  878. (index == 1 ? nsGkAtoms::subscript :
  879. nsGkAtoms::superscript));
  880. } break;
  881. case roles::MATHML_UNDER:
  882. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  883. IndexInParent() == 0 ? nsGkAtoms::base :
  884. nsGkAtoms::underscript);
  885. break;
  886. case roles::MATHML_OVER:
  887. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  888. IndexInParent() == 0 ? nsGkAtoms::base :
  889. nsGkAtoms::overscript);
  890. break;
  891. case roles::MATHML_UNDER_OVER: {
  892. int32_t index = IndexInParent();
  893. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  894. index == 0 ? nsGkAtoms::base :
  895. (index == 1 ? nsGkAtoms::underscript :
  896. nsGkAtoms::overscript));
  897. } break;
  898. case roles::MATHML_MULTISCRIPTS: {
  899. // Get the <multiscripts> base.
  900. nsIContent* child;
  901. bool baseFound = false;
  902. for (child = parent->GetContent()->GetFirstChild(); child;
  903. child = child->GetNextSibling()) {
  904. if (child->IsMathMLElement()) {
  905. baseFound = true;
  906. break;
  907. }
  908. }
  909. if (baseFound) {
  910. nsIContent* content = GetContent();
  911. if (child == content) {
  912. // We are the base.
  913. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  914. nsGkAtoms::base);
  915. } else {
  916. // Browse the list of scripts to find us and determine our type.
  917. bool postscript = true;
  918. bool subscript = true;
  919. for (child = child->GetNextSibling(); child;
  920. child = child->GetNextSibling()) {
  921. if (!child->IsMathMLElement())
  922. continue;
  923. if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) {
  924. postscript = false;
  925. subscript = true;
  926. continue;
  927. }
  928. if (child == content) {
  929. if (postscript) {
  930. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  931. subscript ?
  932. nsGkAtoms::subscript :
  933. nsGkAtoms::superscript);
  934. } else {
  935. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
  936. subscript ?
  937. nsGkAtoms::presubscript :
  938. nsGkAtoms::presuperscript);
  939. }
  940. break;
  941. }
  942. subscript = !subscript;
  943. }
  944. }
  945. }
  946. } break;
  947. default:
  948. break;
  949. }
  950. }
  951. }
  952. already_AddRefed<nsIPersistentProperties>
  953. HyperTextAccessible::NativeAttributes()
  954. {
  955. nsCOMPtr<nsIPersistentProperties> attributes =
  956. AccessibleWrap::NativeAttributes();
  957. // 'formatting' attribute is deprecated, 'display' attribute should be
  958. // instead.
  959. nsIFrame *frame = GetFrame();
  960. if (frame && frame->GetType() == nsGkAtoms::blockFrame) {
  961. nsAutoString unused;
  962. attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"),
  963. NS_LITERAL_STRING("block"), unused);
  964. }
  965. if (FocusMgr()->IsFocused(this)) {
  966. int32_t lineNumber = CaretLineNumber();
  967. if (lineNumber >= 1) {
  968. nsAutoString strLineNumber;
  969. strLineNumber.AppendInt(lineNumber);
  970. nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber);
  971. }
  972. }
  973. if (HasOwnContent()) {
  974. GetAccService()->MarkupAttributes(mContent, attributes);
  975. if (mContent->IsMathMLElement())
  976. SetMathMLXMLRoles(attributes);
  977. }
  978. return attributes.forget();
  979. }
  980. nsIAtom*
  981. HyperTextAccessible::LandmarkRole() const
  982. {
  983. if (!HasOwnContent())
  984. return nullptr;
  985. // For the html landmark elements we expose them like we do ARIA landmarks to
  986. // make AT navigation schemes "just work".
  987. if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
  988. return nsGkAtoms::navigation;
  989. }
  990. if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
  991. nsGkAtoms::footer)) {
  992. // Only map header and footer if they are not descendants of an article
  993. // or section tag.
  994. nsIContent* parent = mContent->GetParent();
  995. while (parent) {
  996. if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::section)) {
  997. break;
  998. }
  999. parent = parent->GetParent();
  1000. }
  1001. // No article or section elements found.
  1002. if (!parent) {
  1003. if (mContent->IsHTMLElement(nsGkAtoms::header)) {
  1004. return nsGkAtoms::banner;
  1005. }
  1006. if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
  1007. return nsGkAtoms::contentinfo;
  1008. }
  1009. }
  1010. return nullptr;
  1011. }
  1012. if (mContent->IsHTMLElement(nsGkAtoms::aside)) {
  1013. return nsGkAtoms::complementary;
  1014. }
  1015. if (mContent->IsHTMLElement(nsGkAtoms::main)) {
  1016. return nsGkAtoms::main;
  1017. }
  1018. return nullptr;
  1019. }
  1020. int32_t
  1021. HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
  1022. {
  1023. nsIFrame* hyperFrame = GetFrame();
  1024. if (!hyperFrame)
  1025. return -1;
  1026. nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType,
  1027. this);
  1028. nsPresContext* presContext = mDoc->PresContext();
  1029. nsPoint coordsInAppUnits =
  1030. ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
  1031. nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
  1032. if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y))
  1033. return -1; // Not found
  1034. nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x,
  1035. coordsInAppUnits.y - frameScreenRect.y);
  1036. // Go through the frames to check if each one has the point.
  1037. // When one does, add up the character offsets until we have a match
  1038. // We have an point in an accessible child of this, now we need to add up the
  1039. // offsets before it to what we already have
  1040. int32_t offset = 0;
  1041. uint32_t childCount = ChildCount();
  1042. for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
  1043. Accessible* childAcc = mChildren[childIdx];
  1044. nsIFrame *primaryFrame = childAcc->GetFrame();
  1045. NS_ENSURE_TRUE(primaryFrame, -1);
  1046. nsIFrame *frame = primaryFrame;
  1047. while (frame) {
  1048. nsIContent *content = frame->GetContent();
  1049. NS_ENSURE_TRUE(content, -1);
  1050. nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
  1051. nsSize frameSize = frame->GetSize();
  1052. if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
  1053. // Finished
  1054. if (frame->GetType() == nsGkAtoms::textFrame) {
  1055. nsIFrame::ContentOffsets contentOffsets =
  1056. frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
  1057. if (contentOffsets.IsNull() || contentOffsets.content != content) {
  1058. return -1; // Not found
  1059. }
  1060. uint32_t addToOffset;
  1061. nsresult rv = ContentToRenderedOffset(primaryFrame,
  1062. contentOffsets.offset,
  1063. &addToOffset);
  1064. NS_ENSURE_SUCCESS(rv, -1);
  1065. offset += addToOffset;
  1066. }
  1067. return offset;
  1068. }
  1069. frame = frame->GetNextContinuation();
  1070. }
  1071. offset += nsAccUtils::TextLength(childAcc);
  1072. }
  1073. return -1; // Not found
  1074. }
  1075. nsIntRect
  1076. HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
  1077. uint32_t aCoordType)
  1078. {
  1079. index_t startOffset = ConvertMagicOffset(aStartOffset);
  1080. index_t endOffset = ConvertMagicOffset(aEndOffset);
  1081. if (!startOffset.IsValid() || !endOffset.IsValid() ||
  1082. startOffset > endOffset || endOffset > CharacterCount()) {
  1083. NS_ERROR("Wrong in offset");
  1084. return nsIntRect();
  1085. }
  1086. int32_t childIdx = GetChildIndexAtOffset(startOffset);
  1087. if (childIdx == -1)
  1088. return nsIntRect();
  1089. nsIntRect bounds;
  1090. int32_t prevOffset = GetChildOffset(childIdx);
  1091. int32_t offset1 = startOffset - prevOffset;
  1092. while (childIdx < static_cast<int32_t>(ChildCount())) {
  1093. nsIFrame* frame = GetChildAt(childIdx++)->GetFrame();
  1094. if (!frame) {
  1095. NS_NOTREACHED("No frame for a child!");
  1096. continue;
  1097. }
  1098. int32_t nextOffset = GetChildOffset(childIdx);
  1099. if (nextOffset >= static_cast<int32_t>(endOffset)) {
  1100. bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
  1101. endOffset - prevOffset));
  1102. break;
  1103. }
  1104. bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
  1105. nextOffset - prevOffset));
  1106. prevOffset = nextOffset;
  1107. offset1 = 0;
  1108. }
  1109. nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this);
  1110. return bounds;
  1111. }
  1112. already_AddRefed<nsIEditor>
  1113. HyperTextAccessible::GetEditor() const
  1114. {
  1115. if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
  1116. // If we're inside an editable container, then return that container's editor
  1117. Accessible* ancestor = Parent();
  1118. while (ancestor) {
  1119. HyperTextAccessible* hyperText = ancestor->AsHyperText();
  1120. if (hyperText) {
  1121. // Recursion will stop at container doc because it has its own impl
  1122. // of GetEditor()
  1123. return hyperText->GetEditor();
  1124. }
  1125. ancestor = ancestor->Parent();
  1126. }
  1127. return nullptr;
  1128. }
  1129. nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
  1130. nsCOMPtr<nsIEditingSession> editingSession;
  1131. docShell->GetEditingSession(getter_AddRefs(editingSession));
  1132. if (!editingSession)
  1133. return nullptr; // No editing session interface
  1134. nsCOMPtr<nsIEditor> editor;
  1135. nsIDocument* docNode = mDoc->DocumentNode();
  1136. editingSession->GetEditorForWindow(docNode->GetWindow(),
  1137. getter_AddRefs(editor));
  1138. return editor.forget();
  1139. }
  1140. /**
  1141. * =================== Caret & Selection ======================
  1142. */
  1143. nsresult
  1144. HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos)
  1145. {
  1146. // Before setting the selection range, we need to ensure that the editor
  1147. // is initialized. (See bug 804927.)
  1148. // Otherwise, it's possible that lazy editor initialization will override
  1149. // the selection we set here and leave the caret at the end of the text.
  1150. // By calling GetEditor here, we ensure that editor initialization is
  1151. // completed before we set the selection.
  1152. nsCOMPtr<nsIEditor> editor = GetEditor();
  1153. bool isFocusable = InteractiveState() & states::FOCUSABLE;
  1154. // If accessible is focusable then focus it before setting the selection to
  1155. // neglect control's selection changes on focus if any (for example, inputs
  1156. // that do select all on focus).
  1157. // some input controls
  1158. if (isFocusable)
  1159. TakeFocus();
  1160. dom::Selection* domSel = DOMSelection();
  1161. NS_ENSURE_STATE(domSel);
  1162. // Set up the selection.
  1163. for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--)
  1164. domSel->RemoveRange(domSel->GetRangeAt(idx));
  1165. SetSelectionBoundsAt(0, aStartPos, aEndPos);
  1166. // When selection is done, move the focus to the selection if accessible is
  1167. // not focusable. That happens when selection is set within hypertext
  1168. // accessible.
  1169. if (isFocusable)
  1170. return NS_OK;
  1171. nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
  1172. if (DOMFocusManager) {
  1173. NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
  1174. nsIDocument* docNode = mDoc->DocumentNode();
  1175. NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
  1176. nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow();
  1177. nsCOMPtr<nsIDOMElement> result;
  1178. DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
  1179. nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result));
  1180. }
  1181. return NS_OK;
  1182. }
  1183. int32_t
  1184. HyperTextAccessible::CaretOffset() const
  1185. {
  1186. // Not focused focusable accessible except document accessible doesn't have
  1187. // a caret.
  1188. if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
  1189. (InteractiveState() & states::FOCUSABLE)) {
  1190. return -1;
  1191. }
  1192. // Check cached value.
  1193. int32_t caretOffset = -1;
  1194. HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset);
  1195. // Use cached value if it corresponds to this accessible.
  1196. if (caretOffset != -1) {
  1197. if (text == this)
  1198. return caretOffset;
  1199. nsINode* textNode = text->GetNode();
  1200. // Ignore offset if cached accessible isn't a text leaf.
  1201. if (nsCoreUtils::IsAncestorOf(GetNode(), textNode))
  1202. return TransformOffset(text,
  1203. textNode->IsNodeOfType(nsINode::eTEXT) ? caretOffset : 0, false);
  1204. }
  1205. // No caret if the focused node is not inside this DOM node and this DOM node
  1206. // is not inside of focused node.
  1207. FocusManager::FocusDisposition focusDisp =
  1208. FocusMgr()->IsInOrContainsFocus(this);
  1209. if (focusDisp == FocusManager::eNone)
  1210. return -1;
  1211. // Turn the focus node and offset of the selection into caret hypretext
  1212. // offset.
  1213. dom::Selection* domSel = DOMSelection();
  1214. NS_ENSURE_TRUE(domSel, -1);
  1215. nsINode* focusNode = domSel->GetFocusNode();
  1216. uint32_t focusOffset = domSel->FocusOffset();
  1217. // No caret if this DOM node is inside of focused node but the selection's
  1218. // focus point is not inside of this DOM node.
  1219. if (focusDisp == FocusManager::eContainedByFocus) {
  1220. nsINode* resultNode =
  1221. nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
  1222. nsINode* thisNode = GetNode();
  1223. if (resultNode != thisNode &&
  1224. !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
  1225. return -1;
  1226. }
  1227. return DOMPointToOffset(focusNode, focusOffset);
  1228. }
  1229. int32_t
  1230. HyperTextAccessible::CaretLineNumber()
  1231. {
  1232. // Provide the line number for the caret, relative to the
  1233. // currently focused node. Use a 1-based index
  1234. RefPtr<nsFrameSelection> frameSelection = FrameSelection();
  1235. if (!frameSelection)
  1236. return -1;
  1237. dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal);
  1238. if (!domSel)
  1239. return - 1;
  1240. nsINode* caretNode = domSel->GetFocusNode();
  1241. if (!caretNode || !caretNode->IsContent())
  1242. return -1;
  1243. nsIContent* caretContent = caretNode->AsContent();
  1244. if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent))
  1245. return -1;
  1246. int32_t returnOffsetUnused;
  1247. uint32_t caretOffset = domSel->FocusOffset();
  1248. CaretAssociationHint hint = frameSelection->GetHint();
  1249. nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
  1250. hint, &returnOffsetUnused);
  1251. NS_ENSURE_TRUE(caretFrame, -1);
  1252. int32_t lineNumber = 1;
  1253. nsAutoLineIterator lineIterForCaret;
  1254. nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr;
  1255. while (caretFrame) {
  1256. if (hyperTextContent == caretFrame->GetContent()) {
  1257. return lineNumber; // Must be in a single line hyper text, there is no line iterator
  1258. }
  1259. nsContainerFrame *parentFrame = caretFrame->GetParent();
  1260. if (!parentFrame)
  1261. break;
  1262. // Add lines for the sibling frames before the caret
  1263. nsIFrame *sibling = parentFrame->PrincipalChildList().FirstChild();
  1264. while (sibling && sibling != caretFrame) {
  1265. nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
  1266. if (lineIterForSibling) {
  1267. // For the frames before that grab all the lines
  1268. int32_t addLines = lineIterForSibling->GetNumLines();
  1269. lineNumber += addLines;
  1270. }
  1271. sibling = sibling->GetNextSibling();
  1272. }
  1273. // Get the line number relative to the container with lines
  1274. if (!lineIterForCaret) { // Add the caret line just once
  1275. lineIterForCaret = parentFrame->GetLineIterator();
  1276. if (lineIterForCaret) {
  1277. // Ancestor of caret
  1278. int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame);
  1279. lineNumber += addLines;
  1280. }
  1281. }
  1282. caretFrame = parentFrame;
  1283. }
  1284. NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
  1285. return lineNumber;
  1286. }
  1287. LayoutDeviceIntRect
  1288. HyperTextAccessible::GetCaretRect(nsIWidget** aWidget)
  1289. {
  1290. *aWidget = nullptr;
  1291. RefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret();
  1292. NS_ENSURE_TRUE(caret, LayoutDeviceIntRect());
  1293. bool isVisible = caret->IsVisible();
  1294. if (!isVisible)
  1295. return LayoutDeviceIntRect();
  1296. nsRect rect;
  1297. nsIFrame* frame = caret->GetGeometry(&rect);
  1298. if (!frame || rect.IsEmpty())
  1299. return LayoutDeviceIntRect();
  1300. nsPoint offset;
  1301. // Offset from widget origin to the frame origin, which includes chrome
  1302. // on the widget.
  1303. *aWidget = frame->GetNearestWidget(offset);
  1304. NS_ENSURE_TRUE(*aWidget, LayoutDeviceIntRect());
  1305. rect.MoveBy(offset);
  1306. LayoutDeviceIntRect caretRect = LayoutDeviceIntRect::FromUnknownRect(
  1307. rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel()));
  1308. // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
  1309. caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset());
  1310. // Correct for character size, so that caret always matches the size of
  1311. // the character. This is important for font size transitions, and is
  1312. // necessary because the Gecko caret uses the previous character's size as
  1313. // the user moves forward in the text by character.
  1314. nsIntRect charRect = CharBounds(CaretOffset(),
  1315. nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
  1316. if (!charRect.IsEmpty()) {
  1317. caretRect.height -= charRect.y - caretRect.y;
  1318. caretRect.y = charRect.y;
  1319. }
  1320. return caretRect;
  1321. }
  1322. void
  1323. HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType,
  1324. nsTArray<nsRange*>* aRanges)
  1325. {
  1326. // Ignore selection if it is not visible.
  1327. RefPtr<nsFrameSelection> frameSelection = FrameSelection();
  1328. if (!frameSelection ||
  1329. frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN)
  1330. return;
  1331. dom::Selection* domSel = frameSelection->GetSelection(aSelectionType);
  1332. if (!domSel)
  1333. return;
  1334. nsCOMPtr<nsINode> startNode = GetNode();
  1335. nsCOMPtr<nsIEditor> editor = GetEditor();
  1336. if (editor) {
  1337. nsCOMPtr<nsIDOMElement> editorRoot;
  1338. editor->GetRootElement(getter_AddRefs(editorRoot));
  1339. startNode = do_QueryInterface(editorRoot);
  1340. }
  1341. if (!startNode)
  1342. return;
  1343. uint32_t childCount = startNode->GetChildCount();
  1344. nsresult rv = domSel->
  1345. GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges);
  1346. NS_ENSURE_SUCCESS_VOID(rv);
  1347. // Remove collapsed ranges
  1348. uint32_t numRanges = aRanges->Length();
  1349. for (uint32_t idx = 0; idx < numRanges; idx ++) {
  1350. if ((*aRanges)[idx]->Collapsed()) {
  1351. aRanges->RemoveElementAt(idx);
  1352. --numRanges;
  1353. --idx;
  1354. }
  1355. }
  1356. }
  1357. int32_t
  1358. HyperTextAccessible::SelectionCount()
  1359. {
  1360. nsTArray<nsRange*> ranges;
  1361. GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
  1362. return ranges.Length();
  1363. }
  1364. bool
  1365. HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
  1366. int32_t* aStartOffset,
  1367. int32_t* aEndOffset)
  1368. {
  1369. *aStartOffset = *aEndOffset = 0;
  1370. nsTArray<nsRange*> ranges;
  1371. GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
  1372. uint32_t rangeCount = ranges.Length();
  1373. if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount))
  1374. return false;
  1375. nsRange* range = ranges[aSelectionNum];
  1376. // Get start and end points.
  1377. nsINode* startNode = range->GetStartParent();
  1378. nsINode* endNode = range->GetEndParent();
  1379. int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset();
  1380. // Make sure start is before end, by swapping DOM points. This occurs when
  1381. // the user selects backwards in the text.
  1382. int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset,
  1383. startNode, startOffset);
  1384. if (rangeCompare < 0) {
  1385. nsINode* tempNode = startNode;
  1386. startNode = endNode;
  1387. endNode = tempNode;
  1388. int32_t tempOffset = startOffset;
  1389. startOffset = endOffset;
  1390. endOffset = tempOffset;
  1391. }
  1392. if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent))
  1393. *aStartOffset = 0;
  1394. else
  1395. *aStartOffset = DOMPointToOffset(startNode, startOffset);
  1396. if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent))
  1397. *aEndOffset = CharacterCount();
  1398. else
  1399. *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
  1400. return true;
  1401. }
  1402. bool
  1403. HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
  1404. int32_t aStartOffset,
  1405. int32_t aEndOffset)
  1406. {
  1407. index_t startOffset = ConvertMagicOffset(aStartOffset);
  1408. index_t endOffset = ConvertMagicOffset(aEndOffset);
  1409. if (!startOffset.IsValid() || !endOffset.IsValid() ||
  1410. startOffset > endOffset || endOffset > CharacterCount()) {
  1411. NS_ERROR("Wrong in offset");
  1412. return false;
  1413. }
  1414. dom::Selection* domSel = DOMSelection();
  1415. if (!domSel)
  1416. return false;
  1417. RefPtr<nsRange> range;
  1418. uint32_t rangeCount = domSel->RangeCount();
  1419. if (aSelectionNum == static_cast<int32_t>(rangeCount))
  1420. range = new nsRange(mContent);
  1421. else
  1422. range = domSel->GetRangeAt(aSelectionNum);
  1423. if (!range)
  1424. return false;
  1425. if (!OffsetsToDOMRange(startOffset, endOffset, range))
  1426. return false;
  1427. // If new range was created then add it, otherwise notify selection listeners
  1428. // that existing selection range was changed.
  1429. if (aSelectionNum == static_cast<int32_t>(rangeCount))
  1430. return NS_SUCCEEDED(domSel->AddRange(range));
  1431. domSel->RemoveRange(range);
  1432. return NS_SUCCEEDED(domSel->AddRange(range));
  1433. }
  1434. bool
  1435. HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum)
  1436. {
  1437. dom::Selection* domSel = DOMSelection();
  1438. if (!domSel)
  1439. return false;
  1440. if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(domSel->RangeCount()))
  1441. return false;
  1442. domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum));
  1443. return true;
  1444. }
  1445. void
  1446. HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
  1447. uint32_t aScrollType)
  1448. {
  1449. RefPtr<nsRange> range = new nsRange(mContent);
  1450. if (OffsetsToDOMRange(aStartOffset, aEndOffset, range))
  1451. nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
  1452. }
  1453. void
  1454. HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
  1455. int32_t aEndOffset,
  1456. uint32_t aCoordinateType,
  1457. int32_t aX, int32_t aY)
  1458. {
  1459. nsIFrame *frame = GetFrame();
  1460. if (!frame)
  1461. return;
  1462. nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
  1463. this);
  1464. RefPtr<nsRange> range = new nsRange(mContent);
  1465. if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range))
  1466. return;
  1467. nsPresContext* presContext = frame->PresContext();
  1468. nsPoint coordsInAppUnits =
  1469. ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
  1470. bool initialScrolled = false;
  1471. nsIFrame *parentFrame = frame;
  1472. while ((parentFrame = parentFrame->GetParent())) {
  1473. nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame);
  1474. if (scrollableFrame) {
  1475. if (!initialScrolled) {
  1476. // Scroll substring to the given point. Turn the point into percents
  1477. // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
  1478. nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
  1479. nscoord offsetPointX = coordsInAppUnits.x - frameRect.x;
  1480. nscoord offsetPointY = coordsInAppUnits.y - frameRect.y;
  1481. nsSize size(parentFrame->GetSize());
  1482. // avoid divide by zero
  1483. size.width = size.width ? size.width : 1;
  1484. size.height = size.height ? size.height : 1;
  1485. int16_t hPercent = offsetPointX * 100 / size.width;
  1486. int16_t vPercent = offsetPointY * 100 / size.height;
  1487. nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range,
  1488. nsIPresShell::ScrollAxis(vPercent),
  1489. nsIPresShell::ScrollAxis(hPercent));
  1490. if (NS_FAILED(rv))
  1491. return;
  1492. initialScrolled = true;
  1493. } else {
  1494. // Substring was scrolled to the given point already inside its closest
  1495. // scrollable area. If there are nested scrollable areas then make
  1496. // sure we scroll lower areas to the given point inside currently
  1497. // traversed scrollable area.
  1498. nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
  1499. }
  1500. }
  1501. frame = parentFrame;
  1502. }
  1503. }
  1504. void
  1505. HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const
  1506. {
  1507. if (IsTextField()) {
  1508. aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0,
  1509. const_cast<HyperTextAccessible*>(this), CharacterCount());
  1510. } else {
  1511. aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount());
  1512. }
  1513. }
  1514. void
  1515. HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const
  1516. {
  1517. MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
  1518. dom::Selection* sel = DOMSelection();
  1519. if (!sel)
  1520. return;
  1521. aRanges->SetCapacity(sel->RangeCount());
  1522. for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
  1523. nsRange* DOMRange = sel->GetRangeAt(idx);
  1524. HyperTextAccessible* startParent =
  1525. nsAccUtils::GetTextContainer(DOMRange->GetStartParent());
  1526. HyperTextAccessible* endParent =
  1527. nsAccUtils::GetTextContainer(DOMRange->GetEndParent());
  1528. if (!startParent || !endParent)
  1529. continue;
  1530. int32_t startOffset =
  1531. startParent->DOMPointToOffset(DOMRange->GetStartParent(),
  1532. DOMRange->StartOffset(), false);
  1533. int32_t endOffset =
  1534. endParent->DOMPointToOffset(DOMRange->GetEndParent(),
  1535. DOMRange->EndOffset(), true);
  1536. TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc,
  1537. startParent, startOffset, endParent, endOffset);
  1538. *(aRanges->AppendElement()) = Move(tr);
  1539. }
  1540. }
  1541. void
  1542. HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const
  1543. {
  1544. }
  1545. void
  1546. HyperTextAccessible::RangeByChild(Accessible* aChild,
  1547. a11y::TextRange& aRange) const
  1548. {
  1549. HyperTextAccessible* ht = aChild->AsHyperText();
  1550. if (ht) {
  1551. aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount());
  1552. return;
  1553. }
  1554. Accessible* child = aChild;
  1555. Accessible* parent = nullptr;
  1556. while ((parent = child->Parent()) && !(ht = parent->AsHyperText()))
  1557. child = parent;
  1558. // If no text then return collapsed text range, otherwise return a range
  1559. // containing the text enclosed by the given child.
  1560. if (ht) {
  1561. int32_t childIdx = child->IndexInParent();
  1562. int32_t startOffset = ht->GetChildOffset(childIdx);
  1563. int32_t endOffset = child->IsTextLeaf() ?
  1564. ht->GetChildOffset(childIdx + 1) : startOffset;
  1565. aRange.Set(mDoc, ht, startOffset, ht, endOffset);
  1566. }
  1567. }
  1568. void
  1569. HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY,
  1570. a11y::TextRange& aRange) const
  1571. {
  1572. Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild);
  1573. if (!child)
  1574. return;
  1575. Accessible* parent = nullptr;
  1576. while ((parent = child->Parent()) && !parent->IsHyperText())
  1577. child = parent;
  1578. // Return collapsed text range for the point.
  1579. if (parent) {
  1580. HyperTextAccessible* ht = parent->AsHyperText();
  1581. int32_t offset = ht->GetChildOffset(child);
  1582. aRange.Set(mDoc, ht, offset, ht, offset);
  1583. }
  1584. }
  1585. ////////////////////////////////////////////////////////////////////////////////
  1586. // Accessible public
  1587. // Accessible protected
  1588. ENameValueFlag
  1589. HyperTextAccessible::NativeName(nsString& aName)
  1590. {
  1591. // Check @alt attribute for invalid img elements.
  1592. bool hasImgAlt = false;
  1593. if (mContent->IsHTMLElement(nsGkAtoms::img)) {
  1594. hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
  1595. if (!aName.IsEmpty())
  1596. return eNameOK;
  1597. }
  1598. ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
  1599. if (!aName.IsEmpty())
  1600. return nameFlag;
  1601. // Get name from title attribute for HTML abbr and acronym elements making it
  1602. // a valid name from markup. Otherwise their name isn't picked up by recursive
  1603. // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
  1604. if (IsAbbreviation() &&
  1605. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName))
  1606. aName.CompressWhitespace();
  1607. return hasImgAlt ? eNoNameOnPurpose : eNameOK;
  1608. }
  1609. void
  1610. HyperTextAccessible::Shutdown()
  1611. {
  1612. mOffsets.Clear();
  1613. AccessibleWrap::Shutdown();
  1614. }
  1615. bool
  1616. HyperTextAccessible::RemoveChild(Accessible* aAccessible)
  1617. {
  1618. int32_t childIndex = aAccessible->IndexInParent();
  1619. int32_t count = mOffsets.Length() - childIndex;
  1620. if (count > 0)
  1621. mOffsets.RemoveElementsAt(childIndex, count);
  1622. return AccessibleWrap::RemoveChild(aAccessible);
  1623. }
  1624. bool
  1625. HyperTextAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
  1626. {
  1627. int32_t count = mOffsets.Length() - aIndex;
  1628. if (count > 0 ) {
  1629. mOffsets.RemoveElementsAt(aIndex, count);
  1630. }
  1631. return AccessibleWrap::InsertChildAt(aIndex, aChild);
  1632. }
  1633. Relation
  1634. HyperTextAccessible::RelationByType(RelationType aType)
  1635. {
  1636. Relation rel = Accessible::RelationByType(aType);
  1637. switch (aType) {
  1638. case RelationType::NODE_CHILD_OF:
  1639. if (HasOwnContent() && mContent->IsMathMLElement()) {
  1640. Accessible* parent = Parent();
  1641. if (parent) {
  1642. nsIContent* parentContent = parent->GetContent();
  1643. if (parentContent &&
  1644. parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
  1645. // Add a relation pointing to the parent <mroot>.
  1646. rel.AppendTarget(parent);
  1647. }
  1648. }
  1649. }
  1650. break;
  1651. case RelationType::NODE_PARENT_OF:
  1652. if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
  1653. Accessible* base = GetChildAt(0);
  1654. Accessible* index = GetChildAt(1);
  1655. if (base && index) {
  1656. // Append the <mroot> children in the order index, base.
  1657. rel.AppendTarget(index);
  1658. rel.AppendTarget(base);
  1659. }
  1660. }
  1661. break;
  1662. default:
  1663. break;
  1664. }
  1665. return rel;
  1666. }
  1667. ////////////////////////////////////////////////////////////////////////////////
  1668. // HyperTextAccessible public static
  1669. nsresult
  1670. HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset,
  1671. uint32_t* aRenderedOffset) const
  1672. {
  1673. if (!aFrame) {
  1674. // Current frame not rendered -- this can happen if text is set on
  1675. // something with display: none
  1676. *aRenderedOffset = 0;
  1677. return NS_OK;
  1678. }
  1679. if (IsTextField()) {
  1680. *aRenderedOffset = aContentOffset;
  1681. return NS_OK;
  1682. }
  1683. NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
  1684. "Need text frame for offset conversion");
  1685. NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
  1686. "Call on primary frame only");
  1687. nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
  1688. aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
  1689. nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
  1690. *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
  1691. return NS_OK;
  1692. }
  1693. nsresult
  1694. HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset,
  1695. int32_t* aContentOffset) const
  1696. {
  1697. if (IsTextField()) {
  1698. *aContentOffset = aRenderedOffset;
  1699. return NS_OK;
  1700. }
  1701. *aContentOffset = 0;
  1702. NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
  1703. NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
  1704. "Need text frame for offset conversion");
  1705. NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
  1706. "Call on primary frame only");
  1707. nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
  1708. aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT,
  1709. nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
  1710. *aContentOffset = text.mOffsetWithinNodeText;
  1711. return NS_OK;
  1712. }
  1713. ////////////////////////////////////////////////////////////////////////////////
  1714. // HyperTextAccessible public
  1715. int32_t
  1716. HyperTextAccessible::GetChildOffset(uint32_t aChildIndex,
  1717. bool aInvalidateAfter) const
  1718. {
  1719. if (aChildIndex == 0) {
  1720. if (aInvalidateAfter)
  1721. mOffsets.Clear();
  1722. return aChildIndex;
  1723. }
  1724. int32_t count = mOffsets.Length() - aChildIndex;
  1725. if (count > 0) {
  1726. if (aInvalidateAfter)
  1727. mOffsets.RemoveElementsAt(aChildIndex, count);
  1728. return mOffsets[aChildIndex - 1];
  1729. }
  1730. uint32_t lastOffset = mOffsets.IsEmpty() ?
  1731. 0 : mOffsets[mOffsets.Length() - 1];
  1732. while (mOffsets.Length() < aChildIndex) {
  1733. Accessible* child = mChildren[mOffsets.Length()];
  1734. lastOffset += nsAccUtils::TextLength(child);
  1735. mOffsets.AppendElement(lastOffset);
  1736. }
  1737. return mOffsets[aChildIndex - 1];
  1738. }
  1739. int32_t
  1740. HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const
  1741. {
  1742. uint32_t lastOffset = 0;
  1743. const uint32_t offsetCount = mOffsets.Length();
  1744. if (offsetCount > 0) {
  1745. lastOffset = mOffsets[offsetCount - 1];
  1746. if (aOffset < lastOffset) {
  1747. size_t index;
  1748. if (BinarySearch(mOffsets, 0, offsetCount, aOffset, &index)) {
  1749. return (index < (offsetCount - 1)) ? index + 1 : index;
  1750. }
  1751. return (index == offsetCount) ? -1 : index;
  1752. }
  1753. }
  1754. uint32_t childCount = ChildCount();
  1755. while (mOffsets.Length() < childCount) {
  1756. Accessible* child = GetChildAt(mOffsets.Length());
  1757. lastOffset += nsAccUtils::TextLength(child);
  1758. mOffsets.AppendElement(lastOffset);
  1759. if (aOffset < lastOffset)
  1760. return mOffsets.Length() - 1;
  1761. }
  1762. if (aOffset == lastOffset)
  1763. return mOffsets.Length() - 1;
  1764. return -1;
  1765. }
  1766. ////////////////////////////////////////////////////////////////////////////////
  1767. // HyperTextAccessible protected
  1768. nsresult
  1769. HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
  1770. Accessible* aAccessible,
  1771. DOMPoint* aPoint)
  1772. {
  1773. NS_ENSURE_ARG(aAccessible);
  1774. if (!aFrame) {
  1775. // If the given frame is null then set offset after the DOM node of the
  1776. // given accessible.
  1777. NS_ASSERTION(!aAccessible->IsDoc(),
  1778. "Shouldn't be called on document accessible!");
  1779. nsIContent* content = aAccessible->GetContent();
  1780. NS_ASSERTION(content, "Shouldn't operate on defunct accessible!");
  1781. nsIContent* parent = content->GetParent();
  1782. aPoint->idx = parent->IndexOf(content) + 1;
  1783. aPoint->node = parent;
  1784. } else if (aFrame->GetType() == nsGkAtoms::textFrame) {
  1785. nsIContent* content = aFrame->GetContent();
  1786. NS_ENSURE_STATE(content);
  1787. nsIFrame *primaryFrame = content->GetPrimaryFrame();
  1788. nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
  1789. NS_ENSURE_SUCCESS(rv, rv);
  1790. aPoint->node = content;
  1791. } else {
  1792. nsIContent* content = aFrame->GetContent();
  1793. NS_ENSURE_STATE(content);
  1794. nsIContent* parent = content->GetParent();
  1795. NS_ENSURE_STATE(parent);
  1796. aPoint->idx = parent->IndexOf(content);
  1797. aPoint->node = parent;
  1798. }
  1799. return NS_OK;
  1800. }
  1801. // HyperTextAccessible
  1802. void
  1803. HyperTextAccessible::GetSpellTextAttr(nsINode* aNode,
  1804. int32_t aNodeOffset,
  1805. uint32_t* aStartOffset,
  1806. uint32_t* aEndOffset,
  1807. nsIPersistentProperties* aAttributes)
  1808. {
  1809. RefPtr<nsFrameSelection> fs = FrameSelection();
  1810. if (!fs)
  1811. return;
  1812. dom::Selection* domSel = fs->GetSelection(SelectionType::eSpellCheck);
  1813. if (!domSel)
  1814. return;
  1815. int32_t rangeCount = domSel->RangeCount();
  1816. if (rangeCount <= 0)
  1817. return;
  1818. uint32_t startOffset = 0, endOffset = 0;
  1819. for (int32_t idx = 0; idx < rangeCount; idx++) {
  1820. nsRange* range = domSel->GetRangeAt(idx);
  1821. if (range->Collapsed())
  1822. continue;
  1823. // See if the point comes after the range in which case we must continue in
  1824. // case there is another range after this one.
  1825. nsINode* endNode = range->GetEndParent();
  1826. int32_t endNodeOffset = range->EndOffset();
  1827. if (nsContentUtils::ComparePoints(aNode, aNodeOffset,
  1828. endNode, endNodeOffset) >= 0)
  1829. continue;
  1830. // At this point our point is either in this range or before it but after
  1831. // the previous range. So we check to see if the range starts before the
  1832. // point in which case the point is in the missspelled range, otherwise it
  1833. // must be before the range and after the previous one if any.
  1834. nsINode* startNode = range->GetStartParent();
  1835. int32_t startNodeOffset = range->StartOffset();
  1836. if (nsContentUtils::ComparePoints(startNode, startNodeOffset, aNode,
  1837. aNodeOffset) <= 0) {
  1838. startOffset = DOMPointToOffset(startNode, startNodeOffset);
  1839. endOffset = DOMPointToOffset(endNode, endNodeOffset);
  1840. if (startOffset > *aStartOffset)
  1841. *aStartOffset = startOffset;
  1842. if (endOffset < *aEndOffset)
  1843. *aEndOffset = endOffset;
  1844. if (aAttributes) {
  1845. nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
  1846. NS_LITERAL_STRING("spelling"));
  1847. }
  1848. return;
  1849. }
  1850. // This range came after the point.
  1851. endOffset = DOMPointToOffset(startNode, startNodeOffset);
  1852. if (idx > 0) {
  1853. nsRange* prevRange = domSel->GetRangeAt(idx - 1);
  1854. startOffset = DOMPointToOffset(prevRange->GetEndParent(),
  1855. prevRange->EndOffset());
  1856. }
  1857. if (startOffset > *aStartOffset)
  1858. *aStartOffset = startOffset;
  1859. if (endOffset < *aEndOffset)
  1860. *aEndOffset = endOffset;
  1861. return;
  1862. }
  1863. // We never found a range that ended after the point, therefore we know that
  1864. // the point is not in a range, that we do not need to compute an end offset,
  1865. // and that we should use the end offset of the last range to compute the
  1866. // start offset of the text attribute range.
  1867. nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1);
  1868. startOffset = DOMPointToOffset(prevRange->GetEndParent(),
  1869. prevRange->EndOffset());
  1870. if (startOffset > *aStartOffset)
  1871. *aStartOffset = startOffset;
  1872. }
  1873. bool
  1874. HyperTextAccessible::IsTextRole()
  1875. {
  1876. const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
  1877. if (roleMapEntry &&
  1878. (roleMapEntry->role == roles::GRAPHIC ||
  1879. roleMapEntry->role == roles::IMAGE_MAP ||
  1880. roleMapEntry->role == roles::SLIDER ||
  1881. roleMapEntry->role == roles::PROGRESSBAR ||
  1882. roleMapEntry->role == roles::SEPARATOR))
  1883. return false;
  1884. return true;
  1885. }