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