1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/generic/HyperTextAccessible.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1942 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sw=2 et tw=78: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "HyperTextAccessible-inl.h" 1.11 + 1.12 +#include "Accessible-inl.h" 1.13 +#include "nsAccessibilityService.h" 1.14 +#include "nsIAccessibleTypes.h" 1.15 +#include "DocAccessible.h" 1.16 +#include "HTMLListAccessible.h" 1.17 +#include "Role.h" 1.18 +#include "States.h" 1.19 +#include "TextAttrs.h" 1.20 +#include "TextRange.h" 1.21 +#include "TreeWalker.h" 1.22 + 1.23 +#include "nsCaret.h" 1.24 +#include "nsContentUtils.h" 1.25 +#include "nsFocusManager.h" 1.26 +#include "nsIDOMRange.h" 1.27 +#include "nsIEditingSession.h" 1.28 +#include "nsIFrame.h" 1.29 +#include "nsFrameSelection.h" 1.30 +#include "nsILineIterator.h" 1.31 +#include "nsIInterfaceRequestorUtils.h" 1.32 +#include "nsIPersistentProperties2.h" 1.33 +#include "nsIScrollableFrame.h" 1.34 +#include "nsIServiceManager.h" 1.35 +#include "nsITextControlElement.h" 1.36 +#include "nsTextFragment.h" 1.37 +#include "mozilla/dom/Element.h" 1.38 +#include "mozilla/EventStates.h" 1.39 +#include "mozilla/dom/Selection.h" 1.40 +#include "mozilla/MathAlgorithms.h" 1.41 +#include "gfxSkipChars.h" 1.42 +#include <algorithm> 1.43 + 1.44 +using namespace mozilla; 1.45 +using namespace mozilla::a11y; 1.46 + 1.47 +//////////////////////////////////////////////////////////////////////////////// 1.48 +// HyperTextAccessible 1.49 +//////////////////////////////////////////////////////////////////////////////// 1.50 + 1.51 +HyperTextAccessible:: 1.52 + HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) : 1.53 + AccessibleWrap(aNode, aDoc), xpcAccessibleHyperText() 1.54 +{ 1.55 + mGenericTypes |= eHyperText; 1.56 +} 1.57 + 1.58 +nsresult 1.59 +HyperTextAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr) 1.60 +{ 1.61 + xpcAccessibleHyperText::QueryInterface(aIID, aInstancePtr); 1.62 + return *aInstancePtr ? NS_OK : Accessible::QueryInterface(aIID, aInstancePtr); 1.63 +} 1.64 +NS_IMPL_ADDREF_INHERITED(HyperTextAccessible, AccessibleWrap) 1.65 +NS_IMPL_RELEASE_INHERITED(HyperTextAccessible, AccessibleWrap) 1.66 + 1.67 +role 1.68 +HyperTextAccessible::NativeRole() 1.69 +{ 1.70 + nsIAtom *tag = mContent->Tag(); 1.71 + 1.72 + if (tag == nsGkAtoms::dd) 1.73 + return roles::DEFINITION; 1.74 + 1.75 + if (tag == nsGkAtoms::form) 1.76 + return roles::FORM; 1.77 + 1.78 + if (tag == nsGkAtoms::blockquote || tag == nsGkAtoms::div || 1.79 + tag == nsGkAtoms::section || tag == nsGkAtoms::nav) 1.80 + return roles::SECTION; 1.81 + 1.82 + if (tag == nsGkAtoms::h1 || tag == nsGkAtoms::h2 || 1.83 + tag == nsGkAtoms::h3 || tag == nsGkAtoms::h4 || 1.84 + tag == nsGkAtoms::h5 || tag == nsGkAtoms::h6) 1.85 + return roles::HEADING; 1.86 + 1.87 + if (tag == nsGkAtoms::article) 1.88 + return roles::DOCUMENT; 1.89 + 1.90 + // Deal with html landmark elements 1.91 + if (tag == nsGkAtoms::header) 1.92 + return roles::HEADER; 1.93 + 1.94 + if (tag == nsGkAtoms::footer) 1.95 + return roles::FOOTER; 1.96 + 1.97 + if (tag == nsGkAtoms::aside) 1.98 + return roles::NOTE; 1.99 + 1.100 + // Treat block frames as paragraphs 1.101 + nsIFrame *frame = GetFrame(); 1.102 + if (frame && frame->GetType() == nsGkAtoms::blockFrame) 1.103 + return roles::PARAGRAPH; 1.104 + 1.105 + return roles::TEXT_CONTAINER; // In ATK this works 1.106 +} 1.107 + 1.108 +uint64_t 1.109 +HyperTextAccessible::NativeState() 1.110 +{ 1.111 + uint64_t states = AccessibleWrap::NativeState(); 1.112 + 1.113 + if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { 1.114 + states |= states::EDITABLE; 1.115 + 1.116 + } else if (mContent->Tag() == nsGkAtoms::article) { 1.117 + // We want <article> to behave like a document in terms of readonly state. 1.118 + states |= states::READONLY; 1.119 + } 1.120 + 1.121 + if (HasChildren()) 1.122 + states |= states::SELECTABLE_TEXT; 1.123 + 1.124 + return states; 1.125 +} 1.126 + 1.127 +nsIntRect 1.128 +HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame, 1.129 + uint32_t aStartRenderedOffset, 1.130 + uint32_t aEndRenderedOffset) 1.131 +{ 1.132 + nsPresContext* presContext = mDoc->PresContext(); 1.133 + if (aFrame->GetType() != nsGkAtoms::textFrame) { 1.134 + return aFrame->GetScreenRectInAppUnits(). 1.135 + ToNearestPixels(presContext->AppUnitsPerDevPixel()); 1.136 + } 1.137 + 1.138 + // Substring must be entirely within the same text node. 1.139 + int32_t startContentOffset, endContentOffset; 1.140 + nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset); 1.141 + NS_ENSURE_SUCCESS(rv, nsIntRect()); 1.142 + rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset); 1.143 + NS_ENSURE_SUCCESS(rv, nsIntRect()); 1.144 + 1.145 + nsIFrame *frame; 1.146 + int32_t startContentOffsetInFrame; 1.147 + // Get the right frame continuation -- not really a child, but a sibling of 1.148 + // the primary frame passed in 1.149 + rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false, 1.150 + &startContentOffsetInFrame, &frame); 1.151 + NS_ENSURE_SUCCESS(rv, nsIntRect()); 1.152 + 1.153 + nsRect screenRect; 1.154 + while (frame && startContentOffset < endContentOffset) { 1.155 + // Start with this frame's screen rect, which we will 1.156 + // shrink based on the substring we care about within it. 1.157 + // We will then add that frame to the total screenRect we 1.158 + // are returning. 1.159 + nsRect frameScreenRect = frame->GetScreenRectInAppUnits(); 1.160 + 1.161 + // Get the length of the substring in this frame that we want the bounds for 1.162 + int32_t startFrameTextOffset, endFrameTextOffset; 1.163 + frame->GetOffsets(startFrameTextOffset, endFrameTextOffset); 1.164 + int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset; 1.165 + int32_t seekLength = endContentOffset - startContentOffset; 1.166 + int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength); 1.167 + 1.168 + // Add the point where the string starts to the frameScreenRect 1.169 + nsPoint frameTextStartPoint; 1.170 + rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint); 1.171 + NS_ENSURE_SUCCESS(rv, nsIntRect()); 1.172 + 1.173 + // Use the point for the end offset to calculate the width 1.174 + nsPoint frameTextEndPoint; 1.175 + rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint); 1.176 + NS_ENSURE_SUCCESS(rv, nsIntRect()); 1.177 + 1.178 + frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x); 1.179 + frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x); 1.180 + 1.181 + screenRect.UnionRect(frameScreenRect, screenRect); 1.182 + 1.183 + // Get ready to loop back for next frame continuation 1.184 + startContentOffset += frameSubStringLength; 1.185 + startContentOffsetInFrame = 0; 1.186 + frame = frame->GetNextContinuation(); 1.187 + } 1.188 + 1.189 + return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel()); 1.190 +} 1.191 + 1.192 +void 1.193 +HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset, 1.194 + nsAString& aText) 1.195 +{ 1.196 + aText.Truncate(); 1.197 + 1.198 + int32_t startOffset = ConvertMagicOffset(aStartOffset); 1.199 + int32_t endOffset = ConvertMagicOffset(aEndOffset); 1.200 + 1.201 + int32_t startChildIdx = GetChildIndexAtOffset(startOffset); 1.202 + if (startChildIdx == -1) 1.203 + return; 1.204 + 1.205 + int32_t endChildIdx = GetChildIndexAtOffset(endOffset); 1.206 + if (endChildIdx == -1) 1.207 + return; 1.208 + 1.209 + if (startChildIdx == endChildIdx) { 1.210 + int32_t childOffset = GetChildOffset(startChildIdx); 1.211 + if (childOffset == -1) 1.212 + return; 1.213 + 1.214 + Accessible* child = GetChildAt(startChildIdx); 1.215 + child->AppendTextTo(aText, startOffset - childOffset, 1.216 + endOffset - startOffset); 1.217 + return; 1.218 + } 1.219 + 1.220 + int32_t startChildOffset = GetChildOffset(startChildIdx); 1.221 + if (startChildOffset == -1) 1.222 + return; 1.223 + 1.224 + Accessible* startChild = GetChildAt(startChildIdx); 1.225 + startChild->AppendTextTo(aText, startOffset - startChildOffset); 1.226 + 1.227 + for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) { 1.228 + Accessible* child = GetChildAt(childIdx); 1.229 + child->AppendTextTo(aText); 1.230 + } 1.231 + 1.232 + int32_t endChildOffset = GetChildOffset(endChildIdx); 1.233 + if (endChildOffset == -1) 1.234 + return; 1.235 + 1.236 + Accessible* endChild = GetChildAt(endChildIdx); 1.237 + endChild->AppendTextTo(aText, 0, endOffset - endChildOffset); 1.238 +} 1.239 + 1.240 +int32_t 1.241 +HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset, 1.242 + bool aIsEndOffset) const 1.243 +{ 1.244 + if (!aNode) 1.245 + return 0; 1.246 + 1.247 + uint32_t offset = 0; 1.248 + nsINode* findNode = nullptr; 1.249 + 1.250 + if (aNodeOffset == -1) { 1.251 + findNode = aNode; 1.252 + 1.253 + } else if (aNode->IsNodeOfType(nsINode::eTEXT)) { 1.254 + // For text nodes, aNodeOffset comes in as a character offset 1.255 + // Text offset will be added at the end, if we find the offset in this hypertext 1.256 + // We want the "skipped" offset into the text (rendered text without the extra whitespace) 1.257 + nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); 1.258 + NS_ENSURE_TRUE(frame, 0); 1.259 + 1.260 + nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset); 1.261 + NS_ENSURE_SUCCESS(rv, 0); 1.262 + // Get the child node and 1.263 + findNode = aNode; 1.264 + 1.265 + } else { 1.266 + // findNode could be null if aNodeOffset == # of child nodes, which means 1.267 + // one of two things: 1.268 + // 1) there are no children, and the passed-in node is not mContent -- use 1.269 + // parentContent for the node to find 1.270 + // 2) there are no children and the passed-in node is mContent, which means 1.271 + // we're an empty nsIAccessibleText 1.272 + // 3) there are children and we're at the end of the children 1.273 + 1.274 + findNode = aNode->GetChildAt(aNodeOffset); 1.275 + if (!findNode) { 1.276 + if (aNodeOffset == 0) { 1.277 + if (aNode == GetNode()) { 1.278 + // Case #1: this accessible has no children and thus has empty text, 1.279 + // we can only be at hypertext offset 0. 1.280 + return 0; 1.281 + } 1.282 + 1.283 + // Case #2: there are no children, we're at this node. 1.284 + findNode = aNode; 1.285 + } else if (aNodeOffset == aNode->GetChildCount()) { 1.286 + // Case #3: we're after the last child, get next node to this one. 1.287 + for (nsINode* tmpNode = aNode; 1.288 + !findNode && tmpNode && tmpNode != mContent; 1.289 + tmpNode = tmpNode->GetParent()) { 1.290 + findNode = tmpNode->GetNextSibling(); 1.291 + } 1.292 + } 1.293 + } 1.294 + } 1.295 + 1.296 + // Get accessible for this findNode, or if that node isn't accessible, use the 1.297 + // accessible for the next DOM node which has one (based on forward depth first search) 1.298 + Accessible* descendant = nullptr; 1.299 + if (findNode) { 1.300 + nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode)); 1.301 + if (findContent && findContent->IsHTML() && 1.302 + findContent->NodeInfo()->Equals(nsGkAtoms::br) && 1.303 + findContent->AttrValueIs(kNameSpaceID_None, 1.304 + nsGkAtoms::mozeditorbogusnode, 1.305 + nsGkAtoms::_true, 1.306 + eIgnoreCase)) { 1.307 + // This <br> is the hacky "bogus node" used when there is no text in a control 1.308 + return 0; 1.309 + } 1.310 + 1.311 + descendant = mDoc->GetAccessible(findNode); 1.312 + if (!descendant && findNode->IsContent()) { 1.313 + Accessible* container = mDoc->GetContainerAccessible(findNode); 1.314 + if (container) { 1.315 + TreeWalker walker(container, findNode->AsContent(), 1.316 + TreeWalker::eWalkContextTree); 1.317 + descendant = walker.NextChild(); 1.318 + } 1.319 + } 1.320 + } 1.321 + 1.322 + return TransformOffset(descendant, offset, aIsEndOffset); 1.323 +} 1.324 + 1.325 +int32_t 1.326 +HyperTextAccessible::TransformOffset(Accessible* aDescendant, 1.327 + int32_t aOffset, bool aIsEndOffset) const 1.328 +{ 1.329 + // From the descendant, go up and get the immediate child of this hypertext. 1.330 + int32_t offset = aOffset; 1.331 + Accessible* descendant = aDescendant; 1.332 + while (descendant) { 1.333 + Accessible* parent = descendant->Parent(); 1.334 + if (parent == this) 1.335 + return GetChildOffset(descendant) + offset; 1.336 + 1.337 + // This offset no longer applies because the passed-in text object is not 1.338 + // a child of the hypertext. This happens when there are nested hypertexts, 1.339 + // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset 1.340 + // to make it relative the hypertext. 1.341 + // If the end offset is not supposed to be inclusive and the original point 1.342 + // is not at 0 offset then the returned offset should be after an embedded 1.343 + // character the original point belongs to. 1.344 + if (aIsEndOffset) 1.345 + offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0; 1.346 + else 1.347 + offset = 0; 1.348 + 1.349 + descendant = parent; 1.350 + } 1.351 + 1.352 + // If the given a11y point cannot be mapped into offset relative this hypertext 1.353 + // offset then return length as fallback value. 1.354 + return CharacterCount(); 1.355 +} 1.356 + 1.357 +bool 1.358 +HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset, 1.359 + nsRange* aRange) 1.360 +{ 1.361 + DOMPoint startPoint = OffsetToDOMPoint(aStartOffset); 1.362 + if (!startPoint.node) 1.363 + return false; 1.364 + 1.365 + aRange->SetStart(startPoint.node, startPoint.idx); 1.366 + if (aStartOffset == aEndOffset) { 1.367 + aRange->SetEnd(startPoint.node, startPoint.idx); 1.368 + return true; 1.369 + } 1.370 + 1.371 + DOMPoint endPoint = OffsetToDOMPoint(aEndOffset); 1.372 + if (!endPoint.node) 1.373 + return false; 1.374 + 1.375 + aRange->SetEnd(endPoint.node, endPoint.idx); 1.376 + return true; 1.377 +} 1.378 + 1.379 +DOMPoint 1.380 +HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset) 1.381 +{ 1.382 + // 0 offset is valid even if no children. In this case the associated editor 1.383 + // is empty so return a DOM point for editor root element. 1.384 + if (aOffset == 0) { 1.385 + nsCOMPtr<nsIEditor> editor = GetEditor(); 1.386 + if (editor) { 1.387 + bool isEmpty = false; 1.388 + editor->GetDocumentIsEmpty(&isEmpty); 1.389 + if (isEmpty) { 1.390 + nsCOMPtr<nsIDOMElement> editorRootElm; 1.391 + editor->GetRootElement(getter_AddRefs(editorRootElm)); 1.392 + 1.393 + nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm)); 1.394 + return DOMPoint(editorRoot, 0); 1.395 + } 1.396 + } 1.397 + } 1.398 + 1.399 + int32_t childIdx = GetChildIndexAtOffset(aOffset); 1.400 + if (childIdx == -1) 1.401 + return DOMPoint(); 1.402 + 1.403 + Accessible* child = GetChildAt(childIdx); 1.404 + int32_t innerOffset = aOffset - GetChildOffset(childIdx); 1.405 + 1.406 + // A text leaf case. The point is inside the text node. 1.407 + if (child->IsTextLeaf()) { 1.408 + nsIContent* content = child->GetContent(); 1.409 + int32_t idx = 0; 1.410 + if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(), 1.411 + innerOffset, &idx))) 1.412 + return DOMPoint(); 1.413 + 1.414 + return DOMPoint(content, idx); 1.415 + } 1.416 + 1.417 + // Case of embedded object. The point is either before or after the element. 1.418 + NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!"); 1.419 + nsINode* node = child->GetNode(); 1.420 + nsINode* parentNode = node->GetParentNode(); 1.421 + return parentNode ? 1.422 + DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) : 1.423 + DOMPoint(); 1.424 +} 1.425 + 1.426 +int32_t 1.427 +HyperTextAccessible::FindOffset(int32_t aOffset, nsDirection aDirection, 1.428 + nsSelectionAmount aAmount, 1.429 + EWordMovementType aWordMovementType) 1.430 +{ 1.431 + // Find a leaf accessible frame to start with. PeekOffset wants this. 1.432 + HyperTextAccessible* text = this; 1.433 + Accessible* child = nullptr; 1.434 + int32_t innerOffset = aOffset; 1.435 + 1.436 + do { 1.437 + int32_t childIdx = text->GetChildIndexAtOffset(innerOffset); 1.438 + NS_ASSERTION(childIdx != -1, "Bad in offset!"); 1.439 + if (childIdx == -1) 1.440 + return -1; 1.441 + 1.442 + child = text->GetChildAt(childIdx); 1.443 + 1.444 + // HTML list items may need special processing because PeekOffset doesn't 1.445 + // work with list bullets. 1.446 + if (text->IsHTMLListItem()) { 1.447 + HTMLLIAccessible* li = text->AsHTMLListItem(); 1.448 + if (child == li->Bullet()) { 1.449 + // It works only when the bullet is one single char. 1.450 + if (aDirection == eDirPrevious) 1.451 + return text != this ? TransformOffset(text, 0, false) : 0; 1.452 + 1.453 + if (aAmount == eSelectEndLine || aAmount == eSelectLine) { 1.454 + if (text != this) 1.455 + return TransformOffset(text, 1, true); 1.456 + 1.457 + // Ask a text leaf next (if not empty) to the bullet for an offset 1.458 + // since list item may be multiline. 1.459 + return aOffset + 1 < CharacterCount() ? 1.460 + FindOffset(aOffset + 1, aDirection, aAmount, aWordMovementType) : 1; 1.461 + } 1.462 + 1.463 + // Case of word and char boundaries. 1.464 + return text != this ? TransformOffset(text, 1, true) : 1; 1.465 + } 1.466 + } 1.467 + 1.468 + innerOffset -= text->GetChildOffset(childIdx); 1.469 + 1.470 + text = child->AsHyperText(); 1.471 + } while (text); 1.472 + 1.473 + nsIFrame* childFrame = child->GetFrame(); 1.474 + NS_ENSURE_TRUE(childFrame, -1); 1.475 + 1.476 + int32_t innerContentOffset = innerOffset; 1.477 + if (child->IsTextLeaf()) { 1.478 + NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!"); 1.479 + RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset); 1.480 + } 1.481 + 1.482 + nsIFrame* frameAtOffset = childFrame; 1.483 + int32_t unusedOffsetInFrame = 0; 1.484 + childFrame->GetChildFrameContainingOffset(innerContentOffset, true, 1.485 + &unusedOffsetInFrame, 1.486 + &frameAtOffset); 1.487 + 1.488 + const bool kIsJumpLinesOk = true; // okay to jump lines 1.489 + const bool kIsScrollViewAStop = false; // do not stop at scroll views 1.490 + const bool kIsKeyboardSelect = true; // is keyboard selection 1.491 + const bool kIsVisualBidi = false; // use visual order for bidi text 1.492 + nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset, 1.493 + 0, kIsJumpLinesOk, kIsScrollViewAStop, 1.494 + kIsKeyboardSelect, kIsVisualBidi, 1.495 + aWordMovementType); 1.496 + nsresult rv = frameAtOffset->PeekOffset(&pos); 1.497 + 1.498 + // PeekOffset fails on last/first lines of the text in certain cases. 1.499 + if (NS_FAILED(rv) && aAmount == eSelectLine) { 1.500 + pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine; 1.501 + frameAtOffset->PeekOffset(&pos); 1.502 + } 1.503 + if (!pos.mResultContent) 1.504 + return -1; 1.505 + 1.506 + // Turn the resulting DOM point into an offset. 1.507 + int32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent, 1.508 + pos.mContentOffset, 1.509 + aDirection == eDirNext); 1.510 + 1.511 + if (aDirection == eDirPrevious) { 1.512 + // If we reached the end during search, this means we didn't find the DOM point 1.513 + // and we're actually at the start of the paragraph 1.514 + if (hyperTextOffset == CharacterCount()) 1.515 + return 0; 1.516 + 1.517 + // PeekOffset stops right before bullet so return 0 to workaround it. 1.518 + if (IsHTMLListItem() && aAmount == eSelectBeginLine && hyperTextOffset == 1) 1.519 + return 0; 1.520 + } 1.521 + 1.522 + return hyperTextOffset; 1.523 +} 1.524 + 1.525 +int32_t 1.526 +HyperTextAccessible::FindLineBoundary(int32_t aOffset, 1.527 + EWhichLineBoundary aWhichLineBoundary) 1.528 +{ 1.529 + // Note: empty last line doesn't have own frame (a previous line contains '\n' 1.530 + // character instead) thus when it makes a difference we need to process this 1.531 + // case separately (otherwise operations are performed on previous line). 1.532 + switch (aWhichLineBoundary) { 1.533 + case ePrevLineBegin: { 1.534 + // Fetch a previous line and move to its start (as arrow up and home keys 1.535 + // were pressed). 1.536 + if (IsEmptyLastLineOffset(aOffset)) 1.537 + return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); 1.538 + 1.539 + int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); 1.540 + return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); 1.541 + } 1.542 + 1.543 + case ePrevLineEnd: { 1.544 + if (IsEmptyLastLineOffset(aOffset)) 1.545 + return aOffset - 1; 1.546 + 1.547 + // If offset is at first line then return 0 (first line start). 1.548 + int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine); 1.549 + if (tmpOffset == 0) 1.550 + return 0; 1.551 + 1.552 + // Otherwise move to end of previous line (as arrow up and end keys were 1.553 + // pressed). 1.554 + tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); 1.555 + return FindOffset(tmpOffset, eDirNext, eSelectEndLine); 1.556 + } 1.557 + 1.558 + case eThisLineBegin: 1.559 + if (IsEmptyLastLineOffset(aOffset)) 1.560 + return aOffset; 1.561 + 1.562 + // Move to begin of the current line (as home key was pressed). 1.563 + return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); 1.564 + 1.565 + case eThisLineEnd: 1.566 + if (IsEmptyLastLineOffset(aOffset)) 1.567 + return aOffset; 1.568 + 1.569 + // Move to end of the current line (as end key was pressed). 1.570 + return FindOffset(aOffset, eDirNext, eSelectEndLine); 1.571 + 1.572 + case eNextLineBegin: { 1.573 + if (IsEmptyLastLineOffset(aOffset)) 1.574 + return aOffset; 1.575 + 1.576 + // Move to begin of the next line if any (arrow down and home keys), 1.577 + // otherwise end of the current line (arrow down only). 1.578 + int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); 1.579 + if (tmpOffset == CharacterCount()) 1.580 + return tmpOffset; 1.581 + 1.582 + return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); 1.583 + } 1.584 + 1.585 + case eNextLineEnd: { 1.586 + if (IsEmptyLastLineOffset(aOffset)) 1.587 + return aOffset; 1.588 + 1.589 + // Move to next line end (as down arrow and end key were pressed). 1.590 + int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); 1.591 + if (tmpOffset != CharacterCount()) 1.592 + return FindOffset(tmpOffset, eDirNext, eSelectEndLine); 1.593 + return tmpOffset; 1.594 + } 1.595 + } 1.596 + 1.597 + return -1; 1.598 +} 1.599 + 1.600 +void 1.601 +HyperTextAccessible::TextBeforeOffset(int32_t aOffset, 1.602 + AccessibleTextBoundary aBoundaryType, 1.603 + int32_t* aStartOffset, int32_t* aEndOffset, 1.604 + nsAString& aText) 1.605 +{ 1.606 + *aStartOffset = *aEndOffset = 0; 1.607 + aText.Truncate(); 1.608 + 1.609 + int32_t convertedOffset = ConvertMagicOffset(aOffset); 1.610 + if (convertedOffset < 0) { 1.611 + NS_ERROR("Wrong given offset!"); 1.612 + return; 1.613 + } 1.614 + 1.615 + int32_t adjustedOffset = convertedOffset; 1.616 + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) 1.617 + adjustedOffset = AdjustCaretOffset(adjustedOffset); 1.618 + 1.619 + switch (aBoundaryType) { 1.620 + case BOUNDARY_CHAR: 1.621 + if (convertedOffset != 0) 1.622 + CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset); 1.623 + break; 1.624 + 1.625 + case BOUNDARY_WORD_START: { 1.626 + // If the offset is a word start (except text length offset) then move 1.627 + // backward to find a start offset (end offset is the given offset). 1.628 + // Otherwise move backward twice to find both start and end offsets. 1.629 + if (adjustedOffset == CharacterCount()) { 1.630 + *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); 1.631 + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); 1.632 + } else { 1.633 + *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); 1.634 + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); 1.635 + if (*aEndOffset != adjustedOffset) { 1.636 + *aEndOffset = *aStartOffset; 1.637 + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); 1.638 + } 1.639 + } 1.640 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.641 + break; 1.642 + } 1.643 + 1.644 + case BOUNDARY_WORD_END: { 1.645 + // Move word backward twice to find start and end offsets. 1.646 + *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord); 1.647 + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); 1.648 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.649 + break; 1.650 + } 1.651 + 1.652 + case BOUNDARY_LINE_START: 1.653 + *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin); 1.654 + *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); 1.655 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.656 + break; 1.657 + 1.658 + case BOUNDARY_LINE_END: { 1.659 + *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); 1.660 + int32_t tmpOffset = *aEndOffset; 1.661 + // Adjust offset if line is wrapped. 1.662 + if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset)) 1.663 + tmpOffset--; 1.664 + 1.665 + *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd); 1.666 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.667 + break; 1.668 + } 1.669 + } 1.670 +} 1.671 + 1.672 +void 1.673 +HyperTextAccessible::TextAtOffset(int32_t aOffset, 1.674 + AccessibleTextBoundary aBoundaryType, 1.675 + int32_t* aStartOffset, int32_t* aEndOffset, 1.676 + nsAString& aText) 1.677 +{ 1.678 + *aStartOffset = *aEndOffset = 0; 1.679 + aText.Truncate(); 1.680 + 1.681 + int32_t adjustedOffset = ConvertMagicOffset(aOffset); 1.682 + if (adjustedOffset < 0) { 1.683 + NS_ERROR("Wrong given offset!"); 1.684 + return; 1.685 + } 1.686 + 1.687 + switch (aBoundaryType) { 1.688 + case BOUNDARY_CHAR: 1.689 + // Return no char if caret is at the end of wrapped line (case of no line 1.690 + // end character). Returning a next line char is confusing for AT. 1.691 + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine()) 1.692 + *aStartOffset = *aEndOffset = adjustedOffset; 1.693 + else 1.694 + CharAt(adjustedOffset, aText, aStartOffset, aEndOffset); 1.695 + break; 1.696 + 1.697 + case BOUNDARY_WORD_START: 1.698 + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) 1.699 + adjustedOffset = AdjustCaretOffset(adjustedOffset); 1.700 + 1.701 + *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); 1.702 + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); 1.703 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.704 + break; 1.705 + 1.706 + case BOUNDARY_WORD_END: 1.707 + // Ignore the spec and follow what WebKitGtk does because Orca expects it, 1.708 + // i.e. return a next word at word end offset of the current word 1.709 + // (WebKitGtk behavior) instead the current word (AKT spec). 1.710 + *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord); 1.711 + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); 1.712 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.713 + break; 1.714 + 1.715 + case BOUNDARY_LINE_START: 1.716 + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) 1.717 + adjustedOffset = AdjustCaretOffset(adjustedOffset); 1.718 + 1.719 + *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); 1.720 + *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); 1.721 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.722 + break; 1.723 + 1.724 + case BOUNDARY_LINE_END: 1.725 + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) 1.726 + adjustedOffset = AdjustCaretOffset(adjustedOffset); 1.727 + 1.728 + // In contrast to word end boundary we follow the spec here. 1.729 + *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); 1.730 + *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); 1.731 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.732 + break; 1.733 + } 1.734 +} 1.735 + 1.736 +void 1.737 +HyperTextAccessible::TextAfterOffset(int32_t aOffset, 1.738 + AccessibleTextBoundary aBoundaryType, 1.739 + int32_t* aStartOffset, int32_t* aEndOffset, 1.740 + nsAString& aText) 1.741 +{ 1.742 + *aStartOffset = *aEndOffset = 0; 1.743 + aText.Truncate(); 1.744 + 1.745 + int32_t convertedOffset = ConvertMagicOffset(aOffset); 1.746 + if (convertedOffset < 0) { 1.747 + NS_ERROR("Wrong given offset!"); 1.748 + return; 1.749 + } 1.750 + 1.751 + int32_t adjustedOffset = convertedOffset; 1.752 + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) 1.753 + adjustedOffset = AdjustCaretOffset(adjustedOffset); 1.754 + 1.755 + switch (aBoundaryType) { 1.756 + case BOUNDARY_CHAR: 1.757 + // If caret is at the end of wrapped line (case of no line end character) 1.758 + // then char after the offset is a first char at next line. 1.759 + if (adjustedOffset >= CharacterCount()) 1.760 + *aStartOffset = *aEndOffset = CharacterCount(); 1.761 + else 1.762 + CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset); 1.763 + break; 1.764 + 1.765 + case BOUNDARY_WORD_START: 1.766 + // Move word forward twice to find start and end offsets. 1.767 + *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); 1.768 + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); 1.769 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.770 + break; 1.771 + 1.772 + case BOUNDARY_WORD_END: 1.773 + // If the offset is a word end (except 0 offset) then move forward to find 1.774 + // end offset (start offset is the given offset). Otherwise move forward 1.775 + // twice to find both start and end offsets. 1.776 + if (convertedOffset == 0) { 1.777 + *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); 1.778 + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); 1.779 + } else { 1.780 + *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); 1.781 + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); 1.782 + if (*aStartOffset != convertedOffset) { 1.783 + *aStartOffset = *aEndOffset; 1.784 + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); 1.785 + } 1.786 + } 1.787 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.788 + break; 1.789 + 1.790 + case BOUNDARY_LINE_START: 1.791 + *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); 1.792 + *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin); 1.793 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.794 + break; 1.795 + 1.796 + case BOUNDARY_LINE_END: 1.797 + *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); 1.798 + *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd); 1.799 + TextSubstring(*aStartOffset, *aEndOffset, aText); 1.800 + break; 1.801 + } 1.802 +} 1.803 + 1.804 +already_AddRefed<nsIPersistentProperties> 1.805 +HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset, 1.806 + int32_t* aStartOffset, 1.807 + int32_t* aEndOffset) 1.808 +{ 1.809 + // 1. Get each attribute and its ranges one after another. 1.810 + // 2. As we get each new attribute, we pass the current start and end offsets 1.811 + // as in/out parameters. In other words, as attributes are collected, 1.812 + // the attribute range itself can only stay the same or get smaller. 1.813 + 1.814 + *aStartOffset = *aEndOffset = 0; 1.815 + nsCOMPtr<nsIPersistentProperties> attributes = 1.816 + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); 1.817 + 1.818 + int32_t offset = ConvertMagicOffset(aOffset); 1.819 + Accessible* accAtOffset = GetChildAtOffset(offset); 1.820 + if (!accAtOffset) { 1.821 + // Offset 0 is correct offset when accessible has empty text. Include 1.822 + // default attributes if they were requested, otherwise return empty set. 1.823 + if (offset == 0) { 1.824 + if (aIncludeDefAttrs) { 1.825 + TextAttrsMgr textAttrsMgr(this); 1.826 + textAttrsMgr.GetAttributes(attributes); 1.827 + } 1.828 + return attributes.forget(); 1.829 + } 1.830 + return nullptr; 1.831 + } 1.832 + 1.833 + int32_t accAtOffsetIdx = accAtOffset->IndexInParent(); 1.834 + int32_t startOffset = GetChildOffset(accAtOffsetIdx); 1.835 + int32_t endOffset = GetChildOffset(accAtOffsetIdx + 1); 1.836 + int32_t offsetInAcc = offset - startOffset; 1.837 + 1.838 + TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset, 1.839 + accAtOffsetIdx); 1.840 + textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset); 1.841 + 1.842 + // Compute spelling attributes on text accessible only. 1.843 + nsIFrame *offsetFrame = accAtOffset->GetFrame(); 1.844 + if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) { 1.845 + int32_t nodeOffset = 0; 1.846 + RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset); 1.847 + 1.848 + // Set 'misspelled' text attribute. 1.849 + GetSpellTextAttribute(accAtOffset->GetNode(), nodeOffset, 1.850 + &startOffset, &endOffset, attributes); 1.851 + } 1.852 + 1.853 + *aStartOffset = startOffset; 1.854 + *aEndOffset = endOffset; 1.855 + return attributes.forget(); 1.856 +} 1.857 + 1.858 +already_AddRefed<nsIPersistentProperties> 1.859 +HyperTextAccessible::DefaultTextAttributes() 1.860 +{ 1.861 + nsCOMPtr<nsIPersistentProperties> attributes = 1.862 + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); 1.863 + 1.864 + TextAttrsMgr textAttrsMgr(this); 1.865 + textAttrsMgr.GetAttributes(attributes); 1.866 + return attributes.forget(); 1.867 +} 1.868 + 1.869 +int32_t 1.870 +HyperTextAccessible::GetLevelInternal() 1.871 +{ 1.872 + nsIAtom *tag = mContent->Tag(); 1.873 + if (tag == nsGkAtoms::h1) 1.874 + return 1; 1.875 + if (tag == nsGkAtoms::h2) 1.876 + return 2; 1.877 + if (tag == nsGkAtoms::h3) 1.878 + return 3; 1.879 + if (tag == nsGkAtoms::h4) 1.880 + return 4; 1.881 + if (tag == nsGkAtoms::h5) 1.882 + return 5; 1.883 + if (tag == nsGkAtoms::h6) 1.884 + return 6; 1.885 + 1.886 + return AccessibleWrap::GetLevelInternal(); 1.887 +} 1.888 + 1.889 +already_AddRefed<nsIPersistentProperties> 1.890 +HyperTextAccessible::NativeAttributes() 1.891 +{ 1.892 + nsCOMPtr<nsIPersistentProperties> attributes = 1.893 + AccessibleWrap::NativeAttributes(); 1.894 + 1.895 + // 'formatting' attribute is deprecated, 'display' attribute should be 1.896 + // instead. 1.897 + nsIFrame *frame = GetFrame(); 1.898 + if (frame && frame->GetType() == nsGkAtoms::blockFrame) { 1.899 + nsAutoString unused; 1.900 + attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"), 1.901 + NS_LITERAL_STRING("block"), unused); 1.902 + } 1.903 + 1.904 + if (FocusMgr()->IsFocused(this)) { 1.905 + int32_t lineNumber = CaretLineNumber(); 1.906 + if (lineNumber >= 1) { 1.907 + nsAutoString strLineNumber; 1.908 + strLineNumber.AppendInt(lineNumber); 1.909 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber); 1.910 + } 1.911 + } 1.912 + 1.913 + if (!HasOwnContent()) 1.914 + return attributes.forget(); 1.915 + 1.916 + // For the html landmark elements we expose them like we do aria landmarks to 1.917 + // make AT navigation schemes "just work". 1.918 + nsIAtom* tag = mContent->Tag(); 1.919 + if (tag == nsGkAtoms::nav) { 1.920 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.921 + NS_LITERAL_STRING("navigation")); 1.922 + } else if (tag == nsGkAtoms::section) { 1.923 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.924 + NS_LITERAL_STRING("region")); 1.925 + } else if (tag == nsGkAtoms::header || tag == nsGkAtoms::footer) { 1.926 + // Only map header and footer if they are not descendants 1.927 + // of an article or section tag. 1.928 + nsIContent* parent = mContent->GetParent(); 1.929 + while (parent) { 1.930 + if (parent->Tag() == nsGkAtoms::article || 1.931 + parent->Tag() == nsGkAtoms::section) 1.932 + break; 1.933 + parent = parent->GetParent(); 1.934 + } 1.935 + 1.936 + // No article or section elements found. 1.937 + if (!parent) { 1.938 + if (tag == nsGkAtoms::header) { 1.939 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.940 + NS_LITERAL_STRING("banner")); 1.941 + } else if (tag == nsGkAtoms::footer) { 1.942 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.943 + NS_LITERAL_STRING("contentinfo")); 1.944 + } 1.945 + } 1.946 + } else if (tag == nsGkAtoms::aside) { 1.947 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.948 + NS_LITERAL_STRING("complementary")); 1.949 + } else if (tag == nsGkAtoms::article) { 1.950 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.951 + NS_LITERAL_STRING("article")); 1.952 + } else if (tag == nsGkAtoms::main) { 1.953 + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, 1.954 + NS_LITERAL_STRING("main")); 1.955 + } 1.956 + 1.957 + return attributes.forget(); 1.958 +} 1.959 + 1.960 +int32_t 1.961 +HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType) 1.962 +{ 1.963 + nsIFrame* hyperFrame = GetFrame(); 1.964 + if (!hyperFrame) 1.965 + return -1; 1.966 + 1.967 + nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, 1.968 + this); 1.969 + 1.970 + nsPresContext* presContext = mDoc->PresContext(); 1.971 + nsPoint coordsInAppUnits = 1.972 + coords.ToAppUnits(presContext->AppUnitsPerDevPixel()); 1.973 + 1.974 + nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits(); 1.975 + if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y)) 1.976 + return -1; // Not found 1.977 + 1.978 + nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x, 1.979 + coordsInAppUnits.y - frameScreenRect.y); 1.980 + 1.981 + // Go through the frames to check if each one has the point. 1.982 + // When one does, add up the character offsets until we have a match 1.983 + 1.984 + // We have an point in an accessible child of this, now we need to add up the 1.985 + // offsets before it to what we already have 1.986 + int32_t offset = 0; 1.987 + uint32_t childCount = ChildCount(); 1.988 + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { 1.989 + Accessible* childAcc = mChildren[childIdx]; 1.990 + 1.991 + nsIFrame *primaryFrame = childAcc->GetFrame(); 1.992 + NS_ENSURE_TRUE(primaryFrame, -1); 1.993 + 1.994 + nsIFrame *frame = primaryFrame; 1.995 + while (frame) { 1.996 + nsIContent *content = frame->GetContent(); 1.997 + NS_ENSURE_TRUE(content, -1); 1.998 + nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame); 1.999 + nsSize frameSize = frame->GetSize(); 1.1000 + if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) { 1.1001 + // Finished 1.1002 + if (frame->GetType() == nsGkAtoms::textFrame) { 1.1003 + nsIFrame::ContentOffsets contentOffsets = 1.1004 + frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE); 1.1005 + if (contentOffsets.IsNull() || contentOffsets.content != content) { 1.1006 + return -1; // Not found 1.1007 + } 1.1008 + uint32_t addToOffset; 1.1009 + nsresult rv = ContentToRenderedOffset(primaryFrame, 1.1010 + contentOffsets.offset, 1.1011 + &addToOffset); 1.1012 + NS_ENSURE_SUCCESS(rv, -1); 1.1013 + offset += addToOffset; 1.1014 + } 1.1015 + return offset; 1.1016 + } 1.1017 + frame = frame->GetNextContinuation(); 1.1018 + } 1.1019 + 1.1020 + offset += nsAccUtils::TextLength(childAcc); 1.1021 + } 1.1022 + 1.1023 + return -1; // Not found 1.1024 +} 1.1025 + 1.1026 +nsIntRect 1.1027 +HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset, 1.1028 + uint32_t aCoordType) 1.1029 +{ 1.1030 + int32_t startOffset = ConvertMagicOffset(aStartOffset); 1.1031 + int32_t endOffset = ConvertMagicOffset(aEndOffset); 1.1032 + NS_ASSERTION(startOffset < endOffset, "Wrong bad in!"); 1.1033 + 1.1034 + int32_t childIdx = GetChildIndexAtOffset(startOffset); 1.1035 + if (childIdx == -1) 1.1036 + return nsIntRect(); 1.1037 + 1.1038 + nsIntRect bounds; 1.1039 + int32_t prevOffset = GetChildOffset(childIdx); 1.1040 + int32_t offset1 = startOffset - prevOffset; 1.1041 + 1.1042 + while (childIdx < ChildCount()) { 1.1043 + nsIFrame* frame = GetChildAt(childIdx++)->GetFrame(); 1.1044 + if (!frame) { 1.1045 + NS_NOTREACHED("No frame for a child!"); 1.1046 + continue; 1.1047 + } 1.1048 + 1.1049 + int32_t nextOffset = GetChildOffset(childIdx); 1.1050 + if (nextOffset >= endOffset) { 1.1051 + bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, 1.1052 + endOffset - prevOffset)); 1.1053 + break; 1.1054 + } 1.1055 + 1.1056 + bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, 1.1057 + nextOffset - prevOffset)); 1.1058 + 1.1059 + prevOffset = nextOffset; 1.1060 + offset1 = 0; 1.1061 + } 1.1062 + 1.1063 + nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this); 1.1064 + return bounds; 1.1065 +} 1.1066 + 1.1067 +already_AddRefed<nsIEditor> 1.1068 +HyperTextAccessible::GetEditor() const 1.1069 +{ 1.1070 + if (!mContent->HasFlag(NODE_IS_EDITABLE)) { 1.1071 + // If we're inside an editable container, then return that container's editor 1.1072 + Accessible* ancestor = Parent(); 1.1073 + while (ancestor) { 1.1074 + HyperTextAccessible* hyperText = ancestor->AsHyperText(); 1.1075 + if (hyperText) { 1.1076 + // Recursion will stop at container doc because it has its own impl 1.1077 + // of GetEditor() 1.1078 + return hyperText->GetEditor(); 1.1079 + } 1.1080 + 1.1081 + ancestor = ancestor->Parent(); 1.1082 + } 1.1083 + 1.1084 + return nullptr; 1.1085 + } 1.1086 + 1.1087 + nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent); 1.1088 + nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(docShell)); 1.1089 + if (!editingSession) 1.1090 + return nullptr; // No editing session interface 1.1091 + 1.1092 + nsCOMPtr<nsIEditor> editor; 1.1093 + nsIDocument* docNode = mDoc->DocumentNode(); 1.1094 + editingSession->GetEditorForWindow(docNode->GetWindow(), 1.1095 + getter_AddRefs(editor)); 1.1096 + return editor.forget(); 1.1097 +} 1.1098 + 1.1099 +/** 1.1100 + * =================== Caret & Selection ====================== 1.1101 + */ 1.1102 + 1.1103 +nsresult 1.1104 +HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos) 1.1105 +{ 1.1106 + // Before setting the selection range, we need to ensure that the editor 1.1107 + // is initialized. (See bug 804927.) 1.1108 + // Otherwise, it's possible that lazy editor initialization will override 1.1109 + // the selection we set here and leave the caret at the end of the text. 1.1110 + // By calling GetEditor here, we ensure that editor initialization is 1.1111 + // completed before we set the selection. 1.1112 + nsCOMPtr<nsIEditor> editor = GetEditor(); 1.1113 + 1.1114 + bool isFocusable = InteractiveState() & states::FOCUSABLE; 1.1115 + 1.1116 + // If accessible is focusable then focus it before setting the selection to 1.1117 + // neglect control's selection changes on focus if any (for example, inputs 1.1118 + // that do select all on focus). 1.1119 + // some input controls 1.1120 + if (isFocusable) 1.1121 + TakeFocus(); 1.1122 + 1.1123 + dom::Selection* domSel = DOMSelection(); 1.1124 + NS_ENSURE_STATE(domSel); 1.1125 + 1.1126 + // Set up the selection. 1.1127 + for (int32_t idx = domSel->GetRangeCount() - 1; idx > 0; idx--) 1.1128 + domSel->RemoveRange(domSel->GetRangeAt(idx)); 1.1129 + SetSelectionBoundsAt(0, aStartPos, aEndPos); 1.1130 + 1.1131 + // When selection is done, move the focus to the selection if accessible is 1.1132 + // not focusable. That happens when selection is set within hypertext 1.1133 + // accessible. 1.1134 + if (isFocusable) 1.1135 + return NS_OK; 1.1136 + 1.1137 + nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); 1.1138 + if (DOMFocusManager) { 1.1139 + NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE); 1.1140 + nsIDocument* docNode = mDoc->DocumentNode(); 1.1141 + NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE); 1.1142 + nsCOMPtr<nsPIDOMWindow> window = docNode->GetWindow(); 1.1143 + nsCOMPtr<nsIDOMElement> result; 1.1144 + DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET, 1.1145 + nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result)); 1.1146 + } 1.1147 + 1.1148 + return NS_OK; 1.1149 +} 1.1150 + 1.1151 +int32_t 1.1152 +HyperTextAccessible::CaretOffset() const 1.1153 +{ 1.1154 + // Not focused focusable accessible except document accessible doesn't have 1.1155 + // a caret. 1.1156 + if (!IsDoc() && !FocusMgr()->IsFocused(this) && 1.1157 + (InteractiveState() & states::FOCUSABLE)) { 1.1158 + return -1; 1.1159 + } 1.1160 + 1.1161 + // No caret if the focused node is not inside this DOM node and this DOM node 1.1162 + // is not inside of focused node. 1.1163 + FocusManager::FocusDisposition focusDisp = 1.1164 + FocusMgr()->IsInOrContainsFocus(this); 1.1165 + if (focusDisp == FocusManager::eNone) 1.1166 + return -1; 1.1167 + 1.1168 + // Turn the focus node and offset of the selection into caret hypretext 1.1169 + // offset. 1.1170 + dom::Selection* domSel = DOMSelection(); 1.1171 + NS_ENSURE_TRUE(domSel, -1); 1.1172 + 1.1173 + nsINode* focusNode = domSel->GetFocusNode(); 1.1174 + uint32_t focusOffset = domSel->FocusOffset(); 1.1175 + 1.1176 + // No caret if this DOM node is inside of focused node but the selection's 1.1177 + // focus point is not inside of this DOM node. 1.1178 + if (focusDisp == FocusManager::eContainedByFocus) { 1.1179 + nsINode* resultNode = 1.1180 + nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset); 1.1181 + 1.1182 + nsINode* thisNode = GetNode(); 1.1183 + if (resultNode != thisNode && 1.1184 + !nsCoreUtils::IsAncestorOf(thisNode, resultNode)) 1.1185 + return -1; 1.1186 + } 1.1187 + 1.1188 + return DOMPointToOffset(focusNode, focusOffset); 1.1189 +} 1.1190 + 1.1191 +int32_t 1.1192 +HyperTextAccessible::CaretLineNumber() 1.1193 +{ 1.1194 + // Provide the line number for the caret, relative to the 1.1195 + // currently focused node. Use a 1-based index 1.1196 + nsRefPtr<nsFrameSelection> frameSelection = FrameSelection(); 1.1197 + if (!frameSelection) 1.1198 + return -1; 1.1199 + 1.1200 + dom::Selection* domSel = 1.1201 + frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL); 1.1202 + if (!domSel) 1.1203 + return - 1; 1.1204 + 1.1205 + nsINode* caretNode = domSel->GetFocusNode(); 1.1206 + if (!caretNode || !caretNode->IsContent()) 1.1207 + return -1; 1.1208 + 1.1209 + nsIContent* caretContent = caretNode->AsContent(); 1.1210 + if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) 1.1211 + return -1; 1.1212 + 1.1213 + int32_t returnOffsetUnused; 1.1214 + uint32_t caretOffset = domSel->FocusOffset(); 1.1215 + nsFrameSelection::HINT hint = frameSelection->GetHint(); 1.1216 + nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset, 1.1217 + hint, &returnOffsetUnused); 1.1218 + NS_ENSURE_TRUE(caretFrame, -1); 1.1219 + 1.1220 + int32_t lineNumber = 1; 1.1221 + nsAutoLineIterator lineIterForCaret; 1.1222 + nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr; 1.1223 + while (caretFrame) { 1.1224 + if (hyperTextContent == caretFrame->GetContent()) { 1.1225 + return lineNumber; // Must be in a single line hyper text, there is no line iterator 1.1226 + } 1.1227 + nsIFrame *parentFrame = caretFrame->GetParent(); 1.1228 + if (!parentFrame) 1.1229 + break; 1.1230 + 1.1231 + // Add lines for the sibling frames before the caret 1.1232 + nsIFrame *sibling = parentFrame->GetFirstPrincipalChild(); 1.1233 + while (sibling && sibling != caretFrame) { 1.1234 + nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator(); 1.1235 + if (lineIterForSibling) { 1.1236 + // For the frames before that grab all the lines 1.1237 + int32_t addLines = lineIterForSibling->GetNumLines(); 1.1238 + lineNumber += addLines; 1.1239 + } 1.1240 + sibling = sibling->GetNextSibling(); 1.1241 + } 1.1242 + 1.1243 + // Get the line number relative to the container with lines 1.1244 + if (!lineIterForCaret) { // Add the caret line just once 1.1245 + lineIterForCaret = parentFrame->GetLineIterator(); 1.1246 + if (lineIterForCaret) { 1.1247 + // Ancestor of caret 1.1248 + int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame); 1.1249 + lineNumber += addLines; 1.1250 + } 1.1251 + } 1.1252 + 1.1253 + caretFrame = parentFrame; 1.1254 + } 1.1255 + 1.1256 + NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't"); 1.1257 + return lineNumber; 1.1258 +} 1.1259 + 1.1260 +nsIntRect 1.1261 +HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) 1.1262 +{ 1.1263 + *aWidget = nullptr; 1.1264 + 1.1265 + nsRefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret(); 1.1266 + NS_ENSURE_TRUE(caret, nsIntRect()); 1.1267 + 1.1268 + nsISelection* caretSelection = caret->GetCaretDOMSelection(); 1.1269 + NS_ENSURE_TRUE(caretSelection, nsIntRect()); 1.1270 + 1.1271 + bool isVisible = false; 1.1272 + caret->GetCaretVisible(&isVisible); 1.1273 + if (!isVisible) 1.1274 + return nsIntRect(); 1.1275 + 1.1276 + nsRect rect; 1.1277 + nsIFrame* frame = caret->GetGeometry(caretSelection, &rect); 1.1278 + if (!frame || rect.IsEmpty()) 1.1279 + return nsIntRect(); 1.1280 + 1.1281 + nsPoint offset; 1.1282 + // Offset from widget origin to the frame origin, which includes chrome 1.1283 + // on the widget. 1.1284 + *aWidget = frame->GetNearestWidget(offset); 1.1285 + NS_ENSURE_TRUE(*aWidget, nsIntRect()); 1.1286 + rect.MoveBy(offset); 1.1287 + 1.1288 + nsIntRect caretRect; 1.1289 + caretRect = rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel()); 1.1290 + // ((content screen origin) - (content offset in the widget)) = widget origin on the screen 1.1291 + caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset()); 1.1292 + 1.1293 + // Correct for character size, so that caret always matches the size of 1.1294 + // the character. This is important for font size transitions, and is 1.1295 + // necessary because the Gecko caret uses the previous character's size as 1.1296 + // the user moves forward in the text by character. 1.1297 + nsIntRect charRect = CharBounds(CaretOffset(), 1.1298 + nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE); 1.1299 + if (!charRect.IsEmpty()) { 1.1300 + caretRect.height -= charRect.y - caretRect.y; 1.1301 + caretRect.y = charRect.y; 1.1302 + } 1.1303 + return caretRect; 1.1304 +} 1.1305 + 1.1306 +void 1.1307 +HyperTextAccessible::GetSelectionDOMRanges(int16_t aType, 1.1308 + nsTArray<nsRange*>* aRanges) 1.1309 +{ 1.1310 + // Ignore selection if it is not visible. 1.1311 + nsRefPtr<nsFrameSelection> frameSelection = FrameSelection(); 1.1312 + if (!frameSelection || 1.1313 + frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN) 1.1314 + return; 1.1315 + 1.1316 + dom::Selection* domSel = frameSelection->GetSelection(aType); 1.1317 + if (!domSel) 1.1318 + return; 1.1319 + 1.1320 + nsCOMPtr<nsINode> startNode = GetNode(); 1.1321 + 1.1322 + nsCOMPtr<nsIEditor> editor = GetEditor(); 1.1323 + if (editor) { 1.1324 + nsCOMPtr<nsIDOMElement> editorRoot; 1.1325 + editor->GetRootElement(getter_AddRefs(editorRoot)); 1.1326 + startNode = do_QueryInterface(editorRoot); 1.1327 + } 1.1328 + 1.1329 + if (!startNode) 1.1330 + return; 1.1331 + 1.1332 + uint32_t childCount = startNode->GetChildCount(); 1.1333 + nsresult rv = domSel-> 1.1334 + GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges); 1.1335 + NS_ENSURE_SUCCESS_VOID(rv); 1.1336 + 1.1337 + // Remove collapsed ranges 1.1338 + uint32_t numRanges = aRanges->Length(); 1.1339 + for (uint32_t idx = 0; idx < numRanges; idx ++) { 1.1340 + if ((*aRanges)[idx]->Collapsed()) { 1.1341 + aRanges->RemoveElementAt(idx); 1.1342 + --numRanges; 1.1343 + --idx; 1.1344 + } 1.1345 + } 1.1346 +} 1.1347 + 1.1348 +int32_t 1.1349 +HyperTextAccessible::SelectionCount() 1.1350 +{ 1.1351 + nsTArray<nsRange*> ranges; 1.1352 + GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges); 1.1353 + return ranges.Length(); 1.1354 +} 1.1355 + 1.1356 +bool 1.1357 +HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum, 1.1358 + int32_t* aStartOffset, 1.1359 + int32_t* aEndOffset) 1.1360 +{ 1.1361 + *aStartOffset = *aEndOffset = 0; 1.1362 + 1.1363 + nsTArray<nsRange*> ranges; 1.1364 + GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges); 1.1365 + 1.1366 + uint32_t rangeCount = ranges.Length(); 1.1367 + if (aSelectionNum < 0 || aSelectionNum >= rangeCount) 1.1368 + return false; 1.1369 + 1.1370 + nsRange* range = ranges[aSelectionNum]; 1.1371 + 1.1372 + // Get start and end points. 1.1373 + nsINode* startNode = range->GetStartParent(); 1.1374 + nsINode* endNode = range->GetEndParent(); 1.1375 + int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset(); 1.1376 + 1.1377 + // Make sure start is before end, by swapping DOM points. This occurs when 1.1378 + // the user selects backwards in the text. 1.1379 + int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset, 1.1380 + startNode, startOffset); 1.1381 + if (rangeCompare < 0) { 1.1382 + nsINode* tempNode = startNode; 1.1383 + startNode = endNode; 1.1384 + endNode = tempNode; 1.1385 + int32_t tempOffset = startOffset; 1.1386 + startOffset = endOffset; 1.1387 + endOffset = tempOffset; 1.1388 + } 1.1389 + 1.1390 + *aStartOffset = DOMPointToOffset(startNode, startOffset); 1.1391 + *aEndOffset = DOMPointToOffset(endNode, endOffset, true); 1.1392 + return true; 1.1393 +} 1.1394 + 1.1395 +bool 1.1396 +HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum, 1.1397 + int32_t aStartOffset, 1.1398 + int32_t aEndOffset) 1.1399 +{ 1.1400 + int32_t startOffset = ConvertMagicOffset(aStartOffset); 1.1401 + int32_t endOffset = ConvertMagicOffset(aEndOffset); 1.1402 + 1.1403 + dom::Selection* domSel = DOMSelection(); 1.1404 + if (!domSel) 1.1405 + return false; 1.1406 + 1.1407 + nsRefPtr<nsRange> range; 1.1408 + uint32_t rangeCount = domSel->GetRangeCount(); 1.1409 + if (aSelectionNum == rangeCount) 1.1410 + range = new nsRange(mContent); 1.1411 + else 1.1412 + range = domSel->GetRangeAt(aSelectionNum); 1.1413 + 1.1414 + if (!range) 1.1415 + return false; 1.1416 + 1.1417 + if (!OffsetsToDOMRange(startOffset, endOffset, range)) 1.1418 + return false; 1.1419 + 1.1420 + // If new range was created then add it, otherwise notify selection listeners 1.1421 + // that existing selection range was changed. 1.1422 + if (aSelectionNum == rangeCount) 1.1423 + return NS_SUCCEEDED(domSel->AddRange(range)); 1.1424 + 1.1425 + domSel->RemoveRange(range); 1.1426 + return NS_SUCCEEDED(domSel->AddRange(range)); 1.1427 +} 1.1428 + 1.1429 +bool 1.1430 +HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum) 1.1431 +{ 1.1432 + dom::Selection* domSel = DOMSelection(); 1.1433 + if (!domSel) 1.1434 + return false; 1.1435 + 1.1436 + if (aSelectionNum < 0 || aSelectionNum >= domSel->GetRangeCount()) 1.1437 + return false; 1.1438 + 1.1439 + domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum)); 1.1440 + return true; 1.1441 +} 1.1442 + 1.1443 +void 1.1444 +HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset, 1.1445 + uint32_t aScrollType) 1.1446 +{ 1.1447 + nsRefPtr<nsRange> range = new nsRange(mContent); 1.1448 + if (OffsetsToDOMRange(aStartOffset, aEndOffset, range)) 1.1449 + nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType); 1.1450 +} 1.1451 + 1.1452 +void 1.1453 +HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset, 1.1454 + int32_t aEndOffset, 1.1455 + uint32_t aCoordinateType, 1.1456 + int32_t aX, int32_t aY) 1.1457 +{ 1.1458 + nsIFrame *frame = GetFrame(); 1.1459 + if (!frame) 1.1460 + return; 1.1461 + 1.1462 + nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, 1.1463 + this); 1.1464 + 1.1465 + nsRefPtr<nsRange> range = new nsRange(mContent); 1.1466 + if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range)) 1.1467 + return; 1.1468 + 1.1469 + nsPresContext* presContext = frame->PresContext(); 1.1470 + nsPoint coordsInAppUnits = 1.1471 + coords.ToAppUnits(presContext->AppUnitsPerDevPixel()); 1.1472 + 1.1473 + bool initialScrolled = false; 1.1474 + nsIFrame *parentFrame = frame; 1.1475 + while ((parentFrame = parentFrame->GetParent())) { 1.1476 + nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame); 1.1477 + if (scrollableFrame) { 1.1478 + if (!initialScrolled) { 1.1479 + // Scroll substring to the given point. Turn the point into percents 1.1480 + // relative scrollable area to use nsCoreUtils::ScrollSubstringTo. 1.1481 + nsRect frameRect = parentFrame->GetScreenRectInAppUnits(); 1.1482 + nscoord offsetPointX = coordsInAppUnits.x - frameRect.x; 1.1483 + nscoord offsetPointY = coordsInAppUnits.y - frameRect.y; 1.1484 + 1.1485 + nsSize size(parentFrame->GetSize()); 1.1486 + 1.1487 + // avoid divide by zero 1.1488 + size.width = size.width ? size.width : 1; 1.1489 + size.height = size.height ? size.height : 1; 1.1490 + 1.1491 + int16_t hPercent = offsetPointX * 100 / size.width; 1.1492 + int16_t vPercent = offsetPointY * 100 / size.height; 1.1493 + 1.1494 + nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range, vPercent, hPercent); 1.1495 + if (NS_FAILED(rv)) 1.1496 + return; 1.1497 + 1.1498 + initialScrolled = true; 1.1499 + } else { 1.1500 + // Substring was scrolled to the given point already inside its closest 1.1501 + // scrollable area. If there are nested scrollable areas then make 1.1502 + // sure we scroll lower areas to the given point inside currently 1.1503 + // traversed scrollable area. 1.1504 + nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); 1.1505 + } 1.1506 + } 1.1507 + frame = parentFrame; 1.1508 + } 1.1509 +} 1.1510 + 1.1511 +void 1.1512 +HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const 1.1513 +{ 1.1514 + if (IsTextField()) { 1.1515 + aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0, 1.1516 + const_cast<HyperTextAccessible*>(this), ChildCount()); 1.1517 + } else { 1.1518 + aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->ChildCount()); 1.1519 + } 1.1520 +} 1.1521 + 1.1522 +void 1.1523 +HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const 1.1524 +{ 1.1525 + NS_ASSERTION(aRanges->Length() != 0, "TextRange array supposed to be empty"); 1.1526 + 1.1527 + dom::Selection* sel = DOMSelection(); 1.1528 + if (!sel) 1.1529 + return; 1.1530 + 1.1531 + aRanges->SetCapacity(sel->RangeCount()); 1.1532 + 1.1533 + for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) { 1.1534 + nsRange* DOMRange = sel->GetRangeAt(idx); 1.1535 + HyperTextAccessible* startParent = 1.1536 + nsAccUtils::GetTextContainer(DOMRange->GetStartParent()); 1.1537 + HyperTextAccessible* endParent = 1.1538 + nsAccUtils::GetTextContainer(DOMRange->GetEndParent()); 1.1539 + if (!startParent || !endParent) 1.1540 + continue; 1.1541 + 1.1542 + int32_t startOffset = 1.1543 + startParent->DOMPointToOffset(DOMRange->GetStartParent(), 1.1544 + DOMRange->StartOffset(), false); 1.1545 + int32_t endOffset = 1.1546 + endParent->DOMPointToOffset(DOMRange->GetEndParent(), 1.1547 + DOMRange->EndOffset(), true); 1.1548 + 1.1549 + TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc, 1.1550 + startParent, startOffset, endParent, endOffset); 1.1551 + *(aRanges->AppendElement()) = Move(tr); 1.1552 + } 1.1553 +} 1.1554 + 1.1555 +void 1.1556 +HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const 1.1557 +{ 1.1558 +} 1.1559 + 1.1560 +void 1.1561 +HyperTextAccessible::RangeByChild(Accessible* aChild, 1.1562 + a11y::TextRange& aRange) const 1.1563 +{ 1.1564 + aRange.Set(mDoc, aChild, 0, aChild, aChild->ChildCount()); 1.1565 +} 1.1566 + 1.1567 +void 1.1568 +HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY, 1.1569 + a11y::TextRange& aRange) const 1.1570 +{ 1.1571 + Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild); 1.1572 + if (child) 1.1573 + aRange.Set(mDoc, child, 0, child, child->ChildCount()); 1.1574 +} 1.1575 + 1.1576 +//////////////////////////////////////////////////////////////////////////////// 1.1577 +// Accessible public 1.1578 + 1.1579 +// Accessible protected 1.1580 +ENameValueFlag 1.1581 +HyperTextAccessible::NativeName(nsString& aName) 1.1582 +{ 1.1583 + // Check @alt attribute for invalid img elements. 1.1584 + bool hasImgAlt = false; 1.1585 + if (mContent->IsHTML(nsGkAtoms::img)) { 1.1586 + hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName); 1.1587 + if (!aName.IsEmpty()) 1.1588 + return eNameOK; 1.1589 + } 1.1590 + 1.1591 + ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName); 1.1592 + if (!aName.IsEmpty()) 1.1593 + return nameFlag; 1.1594 + 1.1595 + // Get name from title attribute for HTML abbr and acronym elements making it 1.1596 + // a valid name from markup. Otherwise their name isn't picked up by recursive 1.1597 + // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP. 1.1598 + if (IsAbbreviation() && 1.1599 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) 1.1600 + aName.CompressWhitespace(); 1.1601 + 1.1602 + return hasImgAlt ? eNoNameOnPurpose : eNameOK; 1.1603 +} 1.1604 + 1.1605 +void 1.1606 +HyperTextAccessible::InvalidateChildren() 1.1607 +{ 1.1608 + mOffsets.Clear(); 1.1609 + 1.1610 + AccessibleWrap::InvalidateChildren(); 1.1611 +} 1.1612 + 1.1613 +bool 1.1614 +HyperTextAccessible::RemoveChild(Accessible* aAccessible) 1.1615 +{ 1.1616 + int32_t childIndex = aAccessible->IndexInParent(); 1.1617 + int32_t count = mOffsets.Length() - childIndex; 1.1618 + if (count > 0) 1.1619 + mOffsets.RemoveElementsAt(childIndex, count); 1.1620 + 1.1621 + return Accessible::RemoveChild(aAccessible); 1.1622 +} 1.1623 + 1.1624 +void 1.1625 +HyperTextAccessible::CacheChildren() 1.1626 +{ 1.1627 + // Trailing HTML br element don't play any difference. We don't need to expose 1.1628 + // it to AT (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=899433#c16 1.1629 + // for details). 1.1630 + 1.1631 + TreeWalker walker(this, mContent); 1.1632 + Accessible* child = nullptr; 1.1633 + Accessible* lastChild = nullptr; 1.1634 + while ((child = walker.NextChild())) { 1.1635 + if (lastChild) 1.1636 + AppendChild(lastChild); 1.1637 + 1.1638 + lastChild = child; 1.1639 + } 1.1640 + 1.1641 + if (lastChild) { 1.1642 + if (lastChild->IsHTMLBr()) 1.1643 + Document()->UnbindFromDocument(lastChild); 1.1644 + else 1.1645 + AppendChild(lastChild); 1.1646 + } 1.1647 +} 1.1648 + 1.1649 +//////////////////////////////////////////////////////////////////////////////// 1.1650 +// HyperTextAccessible public static 1.1651 + 1.1652 +nsresult 1.1653 +HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset, 1.1654 + uint32_t* aRenderedOffset) const 1.1655 +{ 1.1656 + if (!aFrame) { 1.1657 + // Current frame not rendered -- this can happen if text is set on 1.1658 + // something with display: none 1.1659 + *aRenderedOffset = 0; 1.1660 + return NS_OK; 1.1661 + } 1.1662 + 1.1663 + if (IsTextField()) { 1.1664 + *aRenderedOffset = aContentOffset; 1.1665 + return NS_OK; 1.1666 + } 1.1667 + 1.1668 + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, 1.1669 + "Need text frame for offset conversion"); 1.1670 + NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, 1.1671 + "Call on primary frame only"); 1.1672 + 1.1673 + gfxSkipChars skipChars; 1.1674 + gfxSkipCharsIterator iter; 1.1675 + // Only get info up to original offset, we know that will be larger than skipped offset 1.1676 + nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aContentOffset); 1.1677 + NS_ENSURE_SUCCESS(rv, rv); 1.1678 + 1.1679 + uint32_t ourRenderedStart = iter.GetSkippedOffset(); 1.1680 + int32_t ourContentStart = iter.GetOriginalOffset(); 1.1681 + 1.1682 + *aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) - 1.1683 + ourRenderedStart; 1.1684 + 1.1685 + return NS_OK; 1.1686 +} 1.1687 + 1.1688 +nsresult 1.1689 +HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset, 1.1690 + int32_t* aContentOffset) const 1.1691 +{ 1.1692 + if (IsTextField()) { 1.1693 + *aContentOffset = aRenderedOffset; 1.1694 + return NS_OK; 1.1695 + } 1.1696 + 1.1697 + *aContentOffset = 0; 1.1698 + NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE); 1.1699 + 1.1700 + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, 1.1701 + "Need text frame for offset conversion"); 1.1702 + NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, 1.1703 + "Call on primary frame only"); 1.1704 + 1.1705 + gfxSkipChars skipChars; 1.1706 + gfxSkipCharsIterator iter; 1.1707 + // We only need info up to skipped offset -- that is what we're converting to original offset 1.1708 + nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aRenderedOffset); 1.1709 + NS_ENSURE_SUCCESS(rv, rv); 1.1710 + 1.1711 + uint32_t ourRenderedStart = iter.GetSkippedOffset(); 1.1712 + int32_t ourContentStart = iter.GetOriginalOffset(); 1.1713 + 1.1714 + *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart; 1.1715 + 1.1716 + return NS_OK; 1.1717 +} 1.1718 + 1.1719 +//////////////////////////////////////////////////////////////////////////////// 1.1720 +// HyperTextAccessible public 1.1721 + 1.1722 +int32_t 1.1723 +HyperTextAccessible::GetChildOffset(uint32_t aChildIndex, 1.1724 + bool aInvalidateAfter) const 1.1725 +{ 1.1726 + if (aChildIndex == 0) { 1.1727 + if (aInvalidateAfter) 1.1728 + mOffsets.Clear(); 1.1729 + 1.1730 + return aChildIndex; 1.1731 + } 1.1732 + 1.1733 + int32_t count = mOffsets.Length() - aChildIndex; 1.1734 + if (count > 0) { 1.1735 + if (aInvalidateAfter) 1.1736 + mOffsets.RemoveElementsAt(aChildIndex, count); 1.1737 + 1.1738 + return mOffsets[aChildIndex - 1]; 1.1739 + } 1.1740 + 1.1741 + uint32_t lastOffset = mOffsets.IsEmpty() ? 1.1742 + 0 : mOffsets[mOffsets.Length() - 1]; 1.1743 + 1.1744 + while (mOffsets.Length() < aChildIndex) { 1.1745 + Accessible* child = mChildren[mOffsets.Length()]; 1.1746 + lastOffset += nsAccUtils::TextLength(child); 1.1747 + mOffsets.AppendElement(lastOffset); 1.1748 + } 1.1749 + 1.1750 + return mOffsets[aChildIndex - 1]; 1.1751 +} 1.1752 + 1.1753 +int32_t 1.1754 +HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const 1.1755 +{ 1.1756 + uint32_t lastOffset = 0; 1.1757 + uint32_t offsetCount = mOffsets.Length(); 1.1758 + if (offsetCount > 0) { 1.1759 + lastOffset = mOffsets[offsetCount - 1]; 1.1760 + if (aOffset < lastOffset) { 1.1761 + uint32_t low = 0, high = offsetCount; 1.1762 + while (high > low) { 1.1763 + uint32_t mid = (high + low) >> 1; 1.1764 + if (mOffsets[mid] == aOffset) 1.1765 + return mid < offsetCount - 1 ? mid + 1 : mid; 1.1766 + 1.1767 + if (mOffsets[mid] < aOffset) 1.1768 + low = mid + 1; 1.1769 + else 1.1770 + high = mid; 1.1771 + } 1.1772 + if (high == offsetCount) 1.1773 + return -1; 1.1774 + 1.1775 + return low; 1.1776 + } 1.1777 + } 1.1778 + 1.1779 + uint32_t childCount = ChildCount(); 1.1780 + while (mOffsets.Length() < childCount) { 1.1781 + Accessible* child = GetChildAt(mOffsets.Length()); 1.1782 + lastOffset += nsAccUtils::TextLength(child); 1.1783 + mOffsets.AppendElement(lastOffset); 1.1784 + if (aOffset < lastOffset) 1.1785 + return mOffsets.Length() - 1; 1.1786 + } 1.1787 + 1.1788 + if (aOffset == lastOffset) 1.1789 + return mOffsets.Length() - 1; 1.1790 + 1.1791 + return -1; 1.1792 +} 1.1793 + 1.1794 +//////////////////////////////////////////////////////////////////////////////// 1.1795 +// HyperTextAccessible protected 1.1796 + 1.1797 +nsresult 1.1798 +HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset, 1.1799 + Accessible* aAccessible, 1.1800 + DOMPoint* aPoint) 1.1801 +{ 1.1802 + NS_ENSURE_ARG(aAccessible); 1.1803 + 1.1804 + if (!aFrame) { 1.1805 + // If the given frame is null then set offset after the DOM node of the 1.1806 + // given accessible. 1.1807 + NS_ASSERTION(!aAccessible->IsDoc(), 1.1808 + "Shouldn't be called on document accessible!"); 1.1809 + 1.1810 + nsIContent* content = aAccessible->GetContent(); 1.1811 + NS_ASSERTION(content, "Shouldn't operate on defunct accessible!"); 1.1812 + 1.1813 + nsIContent* parent = content->GetParent(); 1.1814 + 1.1815 + aPoint->idx = parent->IndexOf(content) + 1; 1.1816 + aPoint->node = parent; 1.1817 + 1.1818 + } else if (aFrame->GetType() == nsGkAtoms::textFrame) { 1.1819 + nsIContent* content = aFrame->GetContent(); 1.1820 + NS_ENSURE_STATE(content); 1.1821 + 1.1822 + nsIFrame *primaryFrame = content->GetPrimaryFrame(); 1.1823 + nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx)); 1.1824 + NS_ENSURE_SUCCESS(rv, rv); 1.1825 + 1.1826 + aPoint->node = content; 1.1827 + 1.1828 + } else { 1.1829 + nsIContent* content = aFrame->GetContent(); 1.1830 + NS_ENSURE_STATE(content); 1.1831 + 1.1832 + nsIContent* parent = content->GetParent(); 1.1833 + NS_ENSURE_STATE(parent); 1.1834 + 1.1835 + aPoint->idx = parent->IndexOf(content); 1.1836 + aPoint->node = parent; 1.1837 + } 1.1838 + 1.1839 + return NS_OK; 1.1840 +} 1.1841 + 1.1842 +// HyperTextAccessible 1.1843 +nsresult 1.1844 +HyperTextAccessible::GetSpellTextAttribute(nsINode* aNode, 1.1845 + int32_t aNodeOffset, 1.1846 + int32_t* aHTStartOffset, 1.1847 + int32_t* aHTEndOffset, 1.1848 + nsIPersistentProperties* aAttributes) 1.1849 +{ 1.1850 + nsRefPtr<nsFrameSelection> fs = FrameSelection(); 1.1851 + if (!fs) 1.1852 + return NS_OK; 1.1853 + 1.1854 + dom::Selection* domSel = fs->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); 1.1855 + if (!domSel) 1.1856 + return NS_OK; 1.1857 + 1.1858 + int32_t rangeCount = domSel->GetRangeCount(); 1.1859 + if (rangeCount <= 0) 1.1860 + return NS_OK; 1.1861 + 1.1862 + int32_t startHTOffset = 0, endHTOffset = 0; 1.1863 + for (int32_t idx = 0; idx < rangeCount; idx++) { 1.1864 + nsRange* range = domSel->GetRangeAt(idx); 1.1865 + if (range->Collapsed()) 1.1866 + continue; 1.1867 + 1.1868 + // See if the point comes after the range in which case we must continue in 1.1869 + // case there is another range after this one. 1.1870 + nsINode* endNode = range->GetEndParent(); 1.1871 + int32_t endOffset = range->EndOffset(); 1.1872 + if (nsContentUtils::ComparePoints(aNode, aNodeOffset, endNode, endOffset) >= 0) 1.1873 + continue; 1.1874 + 1.1875 + // At this point our point is either in this range or before it but after 1.1876 + // the previous range. So we check to see if the range starts before the 1.1877 + // point in which case the point is in the missspelled range, otherwise it 1.1878 + // must be before the range and after the previous one if any. 1.1879 + nsINode* startNode = range->GetStartParent(); 1.1880 + int32_t startOffset = range->StartOffset(); 1.1881 + if (nsContentUtils::ComparePoints(startNode, startOffset, aNode, 1.1882 + aNodeOffset) <= 0) { 1.1883 + startHTOffset = DOMPointToOffset(startNode, startOffset); 1.1884 + 1.1885 + endHTOffset = DOMPointToOffset(endNode, endOffset); 1.1886 + 1.1887 + if (startHTOffset > *aHTStartOffset) 1.1888 + *aHTStartOffset = startHTOffset; 1.1889 + 1.1890 + if (endHTOffset < *aHTEndOffset) 1.1891 + *aHTEndOffset = endHTOffset; 1.1892 + 1.1893 + if (aAttributes) { 1.1894 + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, 1.1895 + NS_LITERAL_STRING("spelling")); 1.1896 + } 1.1897 + 1.1898 + return NS_OK; 1.1899 + } 1.1900 + 1.1901 + // This range came after the point. 1.1902 + endHTOffset = DOMPointToOffset(startNode, startOffset); 1.1903 + 1.1904 + if (idx > 0) { 1.1905 + nsRange* prevRange = domSel->GetRangeAt(idx - 1); 1.1906 + startHTOffset = DOMPointToOffset(prevRange->GetEndParent(), 1.1907 + prevRange->EndOffset()); 1.1908 + } 1.1909 + 1.1910 + if (startHTOffset > *aHTStartOffset) 1.1911 + *aHTStartOffset = startHTOffset; 1.1912 + 1.1913 + if (endHTOffset < *aHTEndOffset) 1.1914 + *aHTEndOffset = endHTOffset; 1.1915 + 1.1916 + return NS_OK; 1.1917 + } 1.1918 + 1.1919 + // We never found a range that ended after the point, therefore we know that 1.1920 + // the point is not in a range, that we do not need to compute an end offset, 1.1921 + // and that we should use the end offset of the last range to compute the 1.1922 + // start offset of the text attribute range. 1.1923 + nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1); 1.1924 + startHTOffset = DOMPointToOffset(prevRange->GetEndParent(), 1.1925 + prevRange->EndOffset()); 1.1926 + 1.1927 + if (startHTOffset > *aHTStartOffset) 1.1928 + *aHTStartOffset = startHTOffset; 1.1929 + 1.1930 + return NS_OK; 1.1931 +} 1.1932 + 1.1933 +bool 1.1934 +HyperTextAccessible::IsTextRole() 1.1935 +{ 1.1936 + if (mRoleMapEntry && 1.1937 + (mRoleMapEntry->role == roles::GRAPHIC || 1.1938 + mRoleMapEntry->role == roles::IMAGE_MAP || 1.1939 + mRoleMapEntry->role == roles::SLIDER || 1.1940 + mRoleMapEntry->role == roles::PROGRESSBAR || 1.1941 + mRoleMapEntry->role == roles::SEPARATOR)) 1.1942 + return false; 1.1943 + 1.1944 + return true; 1.1945 +}