forked from mirrors/gecko-dev
		
	 657bc8f9d5
			
		
	
	
		657bc8f9d5
		
	
	
	
	
		
			
			These loops may run script. In `HyperTextAccessible`, it calls `RemoveRangeAndUnselectFramesAndNotifyListeners`. So, chrome script which is not directly related to this module may run in each call. I think that using `RemoveAllRanges` is better for here. Before bug 1735446, this loop tried to keep first range, but accidentally, I changed this loop delete all ranges. However, `SetSelectionBoundsAt` will add a range if it's required and there is no bug reports. Therefore, I think keep the new behavior is better. In `nsRange::DeleteFromDocument`, the loop may run mutation events too. Therefore, it needs to store all ranges first. Then, the preceding patch changes the behavior here if a selection range is moved to different root. Previously, it was deleted, but now, they are not touched. Depends on D200606 Differential Revision: https://phabricator.services.mozilla.com/D200607
		
			
				
	
	
		
			1141 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1141 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=2 sw=2 et tw=78: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "HyperTextAccessible-inl.h"
 | |
| 
 | |
| #include "nsAccessibilityService.h"
 | |
| #include "nsIAccessibleTypes.h"
 | |
| #include "AccAttributes.h"
 | |
| #include "HTMLListAccessible.h"
 | |
| #include "LocalAccessible-inl.h"
 | |
| #include "Relation.h"
 | |
| #include "mozilla/a11y/Role.h"
 | |
| #include "States.h"
 | |
| #include "TextAttrs.h"
 | |
| #include "TextRange.h"
 | |
| #include "TreeWalker.h"
 | |
| 
 | |
| #include "nsCaret.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsDebug.h"
 | |
| #include "nsFocusManager.h"
 | |
| #include "nsIEditingSession.h"
 | |
| #include "nsContainerFrame.h"
 | |
| #include "nsFrameSelection.h"
 | |
| #include "nsILineIterator.h"
 | |
| #include "nsIScrollableFrame.h"
 | |
| #include "nsIMathMLFrame.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsRange.h"
 | |
| #include "mozilla/Assertions.h"
 | |
| #include "mozilla/EditorBase.h"
 | |
| #include "mozilla/HTMLEditor.h"
 | |
| #include "mozilla/IntegerRange.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/SelectionMovementUtils.h"
 | |
| #include "mozilla/dom/Element.h"
 | |
| #include "mozilla/dom/HTMLBRElement.h"
 | |
| #include "mozilla/dom/Selection.h"
 | |
| #include "gfxSkipChars.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::a11y;
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // HyperTextAccessible
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| HyperTextAccessible::HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc)
 | |
|     : AccessibleWrap(aNode, aDoc) {
 | |
|   mType = eHyperTextType;
 | |
|   mGenericTypes |= eHyperText;
 | |
| }
 | |
| 
 | |
| role HyperTextAccessible::NativeRole() const {
 | |
|   a11y::role r = GetAccService()->MarkupRole(mContent);
 | |
|   if (r != roles::NOTHING) return r;
 | |
| 
 | |
|   nsIFrame* frame = GetFrame();
 | |
|   if (frame && frame->IsInlineFrame()) return roles::TEXT;
 | |
| 
 | |
|   return roles::TEXT_CONTAINER;
 | |
| }
 | |
| 
 | |
| uint64_t HyperTextAccessible::NativeState() const {
 | |
|   uint64_t states = AccessibleWrap::NativeState();
 | |
| 
 | |
|   if (IsEditable()) {
 | |
|     states |= states::EDITABLE;
 | |
| 
 | |
|   } else if (mContent->IsHTMLElement(nsGkAtoms::article)) {
 | |
|     // We want <article> to behave like a document in terms of readonly state.
 | |
|     states |= states::READONLY;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = GetFrame();
 | |
|   if ((states & states::EDITABLE) || (frame && frame->IsSelectable(nullptr))) {
 | |
|     // If the accessible is editable the layout selectable state only disables
 | |
|     // mouse selection, but keyboard (shift+arrow) selection is still possible.
 | |
|     states |= states::SELECTABLE_TEXT;
 | |
|   }
 | |
| 
 | |
|   return states;
 | |
| }
 | |
| 
 | |
| bool HyperTextAccessible::IsEditable() const {
 | |
|   if (!mContent) {
 | |
|     return false;
 | |
|   }
 | |
|   return mContent->AsElement()->State().HasState(dom::ElementState::READWRITE);
 | |
| }
 | |
| 
 | |
| uint32_t HyperTextAccessible::DOMPointToOffset(nsINode* aNode,
 | |
|                                                int32_t aNodeOffset,
 | |
|                                                bool aIsEndOffset) const {
 | |
|   if (!aNode) return 0;
 | |
| 
 | |
|   uint32_t offset = 0;
 | |
|   nsINode* findNode = nullptr;
 | |
| 
 | |
|   if (aNodeOffset == -1) {
 | |
|     findNode = aNode;
 | |
| 
 | |
|   } else if (aNode->IsText()) {
 | |
|     // For text nodes, aNodeOffset comes in as a character offset
 | |
|     // Text offset will be added at the end, if we find the offset in this
 | |
|     // hypertext We want the "skipped" offset into the text (rendered text
 | |
|     // without the extra whitespace)
 | |
|     nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
 | |
|     NS_ENSURE_TRUE(frame, 0);
 | |
| 
 | |
|     nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
 | |
|     NS_ENSURE_SUCCESS(rv, 0);
 | |
| 
 | |
|     findNode = aNode;
 | |
| 
 | |
|   } else {
 | |
|     // findNode could be null if aNodeOffset == # of child nodes, which means
 | |
|     // one of two things:
 | |
|     // 1) there are no children, and the passed-in node is not mContent -- use
 | |
|     //    parentContent for the node to find
 | |
|     // 2) there are no children and the passed-in node is mContent, which means
 | |
|     //    we're an empty nsIAccessibleText
 | |
|     // 3) there are children and we're at the end of the children
 | |
| 
 | |
|     findNode = aNode->GetChildAt_Deprecated(aNodeOffset);
 | |
|     if (!findNode) {
 | |
|       if (aNodeOffset == 0) {
 | |
|         if (aNode == GetNode()) {
 | |
|           // Case #1: this accessible has no children and thus has empty text,
 | |
|           // we can only be at hypertext offset 0.
 | |
|           return 0;
 | |
|         }
 | |
| 
 | |
|         // Case #2: there are no children, we're at this node.
 | |
|         findNode = aNode;
 | |
|       } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) {
 | |
|         // Case #3: we're after the last child, get next node to this one.
 | |
|         for (nsINode* tmpNode = aNode;
 | |
|              !findNode && tmpNode && tmpNode != mContent;
 | |
|              tmpNode = tmpNode->GetParent()) {
 | |
|           findNode = tmpNode->GetNextSibling();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Get accessible for this findNode, or if that node isn't accessible, use the
 | |
|   // accessible for the next DOM node which has one (based on forward depth
 | |
|   // first search)
 | |
|   LocalAccessible* descendant = nullptr;
 | |
|   if (findNode) {
 | |
|     dom::HTMLBRElement* brElement = dom::HTMLBRElement::FromNode(findNode);
 | |
|     if (brElement && brElement->IsPaddingForEmptyEditor()) {
 | |
|       // This <br> is the hacky "padding <br> element" used when there is no
 | |
|       // text in the editor.
 | |
|       return 0;
 | |
|     }
 | |
| 
 | |
|     descendant = mDoc->GetAccessible(findNode);
 | |
|     if (!descendant && findNode->IsContent()) {
 | |
|       LocalAccessible* container = mDoc->GetContainerAccessible(findNode);
 | |
|       if (container) {
 | |
|         TreeWalker walker(container, findNode->AsContent(),
 | |
|                           TreeWalker::eWalkContextTree);
 | |
|         descendant = walker.Next();
 | |
|         if (!descendant) descendant = container;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return TransformOffset(descendant, offset, aIsEndOffset);
 | |
| }
 | |
| 
 | |
| uint32_t HyperTextAccessible::TransformOffset(LocalAccessible* aDescendant,
 | |
|                                               uint32_t aOffset,
 | |
|                                               bool aIsEndOffset) const {
 | |
|   // From the descendant, go up and get the immediate child of this hypertext.
 | |
|   uint32_t offset = aOffset;
 | |
|   LocalAccessible* descendant = aDescendant;
 | |
|   while (descendant) {
 | |
|     LocalAccessible* parent = descendant->LocalParent();
 | |
|     if (parent == this) return GetChildOffset(descendant) + offset;
 | |
| 
 | |
|     // This offset no longer applies because the passed-in text object is not
 | |
|     // a child of the hypertext. This happens when there are nested hypertexts,
 | |
|     // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
 | |
|     // to make it relative the hypertext.
 | |
|     // If the end offset is not supposed to be inclusive and the original point
 | |
|     // is not at 0 offset then the returned offset should be after an embedded
 | |
|     // character the original point belongs to.
 | |
|     if (aIsEndOffset) {
 | |
|       // Similar to our special casing in FindOffset, we add handling for
 | |
|       // bulleted lists here because PeekOffset returns the inner text node
 | |
|       // for a list when it should return the list bullet.
 | |
|       // We manually set the offset so the error doesn't propagate up.
 | |
|       if (offset == 0 && parent && parent->IsHTMLListItem() &&
 | |
|           descendant->LocalPrevSibling() &&
 | |
|           descendant->LocalPrevSibling() ==
 | |
|               parent->AsHTMLListItem()->Bullet()) {
 | |
|         offset = 0;
 | |
|       } else {
 | |
|         offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
 | |
|       }
 | |
|     } else {
 | |
|       offset = 0;
 | |
|     }
 | |
| 
 | |
|     descendant = parent;
 | |
|   }
 | |
| 
 | |
|   // If the given a11y point cannot be mapped into offset relative this
 | |
|   // hypertext offset then return length as fallback value.
 | |
|   return CharacterCount();
 | |
| }
 | |
| 
 | |
| DOMPoint HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset) const {
 | |
|   // 0 offset is valid even if no children. In this case the associated editor
 | |
|   // is empty so return a DOM point for editor root element.
 | |
|   if (aOffset == 0) {
 | |
|     RefPtr<EditorBase> editorBase = GetEditor();
 | |
|     if (editorBase) {
 | |
|       if (editorBase->IsEmpty()) {
 | |
|         return DOMPoint(editorBase->GetRoot(), 0);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   int32_t childIdx = GetChildIndexAtOffset(aOffset);
 | |
|   if (childIdx == -1) return DOMPoint();
 | |
| 
 | |
|   LocalAccessible* child = LocalChildAt(childIdx);
 | |
|   int32_t innerOffset = aOffset - GetChildOffset(childIdx);
 | |
| 
 | |
|   // A text leaf case.
 | |
|   if (child->IsTextLeaf()) {
 | |
|     // The point is inside the text node. This is always true for any text leaf
 | |
|     // except a last child one. See assertion below.
 | |
|     if (aOffset < GetChildOffset(childIdx + 1)) {
 | |
|       nsIContent* content = child->GetContent();
 | |
|       int32_t idx = 0;
 | |
|       if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
 | |
|                                             innerOffset, &idx))) {
 | |
|         return DOMPoint();
 | |
|       }
 | |
| 
 | |
|       return DOMPoint(content, idx);
 | |
|     }
 | |
| 
 | |
|     // Set the DOM point right after the text node.
 | |
|     MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
 | |
|     innerOffset = 1;
 | |
|   }
 | |
| 
 | |
|   // Case of embedded object. The point is either before or after the element.
 | |
|   NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
 | |
|   nsINode* node = child->GetNode();
 | |
|   nsINode* parentNode = node->GetParentNode();
 | |
|   return parentNode ? DOMPoint(parentNode,
 | |
|                                parentNode->ComputeIndexOf_Deprecated(node) +
 | |
|                                    innerOffset)
 | |
|                     : DOMPoint();
 | |
| }
 | |
| 
 | |
| already_AddRefed<AccAttributes> HyperTextAccessible::DefaultTextAttributes() {
 | |
|   RefPtr<AccAttributes> attributes = new AccAttributes();
 | |
| 
 | |
|   TextAttrsMgr textAttrsMgr(this);
 | |
|   textAttrsMgr.GetAttributes(attributes);
 | |
|   return attributes.forget();
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::SetMathMLXMLRoles(AccAttributes* aAttributes) {
 | |
|   // Add MathML xmlroles based on the position inside the parent.
 | |
|   LocalAccessible* parent = LocalParent();
 | |
|   if (parent) {
 | |
|     switch (parent->Role()) {
 | |
|       case roles::MATHML_CELL:
 | |
|       case roles::MATHML_ENCLOSED:
 | |
|       case roles::MATHML_ERROR:
 | |
|       case roles::MATHML_MATH:
 | |
|       case roles::MATHML_ROW:
 | |
|       case roles::MATHML_SQUARE_ROOT:
 | |
|       case roles::MATHML_STYLE:
 | |
|         if (Role() == roles::MATHML_OPERATOR) {
 | |
|           // This is an operator inside an <mrow> (or an inferred <mrow>).
 | |
|           // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
 | |
|           // XXX We should probably do something similar for MATHML_FENCED, but
 | |
|           // operators do not appear in the accessible tree. See bug 1175747.
 | |
|           nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame());
 | |
|           if (mathMLFrame) {
 | |
|             nsEmbellishData embellishData;
 | |
|             mathMLFrame->GetEmbellishData(embellishData);
 | |
|             if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) {
 | |
|               if (!LocalPrevSibling()) {
 | |
|                 aAttributes->SetAttribute(nsGkAtoms::xmlroles,
 | |
|                                           nsGkAtoms::open_fence);
 | |
|               } else if (!LocalNextSibling()) {
 | |
|                 aAttributes->SetAttribute(nsGkAtoms::xmlroles,
 | |
|                                           nsGkAtoms::close_fence);
 | |
|               }
 | |
|             }
 | |
|             if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) {
 | |
|               aAttributes->SetAttribute(nsGkAtoms::xmlroles,
 | |
|                                         nsGkAtoms::separator_);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         break;
 | |
|       case roles::MATHML_FRACTION:
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles, IndexInParent() == 0 ? nsGkAtoms::numerator
 | |
|                                                       : nsGkAtoms::denominator);
 | |
|         break;
 | |
|       case roles::MATHML_ROOT:
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles,
 | |
|             IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::root_index);
 | |
|         break;
 | |
|       case roles::MATHML_SUB:
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles,
 | |
|             IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::subscript);
 | |
|         break;
 | |
|       case roles::MATHML_SUP:
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles,
 | |
|             IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::superscript);
 | |
|         break;
 | |
|       case roles::MATHML_SUB_SUP: {
 | |
|         int32_t index = IndexInParent();
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles,
 | |
|             index == 0
 | |
|                 ? nsGkAtoms::base
 | |
|                 : (index == 1 ? nsGkAtoms::subscript : nsGkAtoms::superscript));
 | |
|       } break;
 | |
|       case roles::MATHML_UNDER:
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles,
 | |
|             IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::underscript);
 | |
|         break;
 | |
|       case roles::MATHML_OVER:
 | |
|         aAttributes->SetAttribute(
 | |
|             nsGkAtoms::xmlroles,
 | |
|             IndexInParent() == 0 ? nsGkAtoms::base : nsGkAtoms::overscript);
 | |
|         break;
 | |
|       case roles::MATHML_UNDER_OVER: {
 | |
|         int32_t index = IndexInParent();
 | |
|         aAttributes->SetAttribute(nsGkAtoms::xmlroles,
 | |
|                                   index == 0
 | |
|                                       ? nsGkAtoms::base
 | |
|                                       : (index == 1 ? nsGkAtoms::underscript
 | |
|                                                     : nsGkAtoms::overscript));
 | |
|       } break;
 | |
|       case roles::MATHML_MULTISCRIPTS: {
 | |
|         // Get the <multiscripts> base.
 | |
|         nsIContent* child;
 | |
|         bool baseFound = false;
 | |
|         for (child = parent->GetContent()->GetFirstChild(); child;
 | |
|              child = child->GetNextSibling()) {
 | |
|           if (child->IsMathMLElement()) {
 | |
|             baseFound = true;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|         if (baseFound) {
 | |
|           nsIContent* content = GetContent();
 | |
|           if (child == content) {
 | |
|             // We are the base.
 | |
|             aAttributes->SetAttribute(nsGkAtoms::xmlroles, nsGkAtoms::base);
 | |
|           } else {
 | |
|             // Browse the list of scripts to find us and determine our type.
 | |
|             bool postscript = true;
 | |
|             bool subscript = true;
 | |
|             for (child = child->GetNextSibling(); child;
 | |
|                  child = child->GetNextSibling()) {
 | |
|               if (!child->IsMathMLElement()) continue;
 | |
|               if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) {
 | |
|                 postscript = false;
 | |
|                 subscript = true;
 | |
|                 continue;
 | |
|               }
 | |
|               if (child == content) {
 | |
|                 if (postscript) {
 | |
|                   aAttributes->SetAttribute(nsGkAtoms::xmlroles,
 | |
|                                             subscript ? nsGkAtoms::subscript
 | |
|                                                       : nsGkAtoms::superscript);
 | |
|                 } else {
 | |
|                   aAttributes->SetAttribute(nsGkAtoms::xmlroles,
 | |
|                                             subscript
 | |
|                                                 ? nsGkAtoms::presubscript
 | |
|                                                 : nsGkAtoms::presuperscript);
 | |
|                 }
 | |
|                 break;
 | |
|               }
 | |
|               subscript = !subscript;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       } break;
 | |
|       default:
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| already_AddRefed<AccAttributes> HyperTextAccessible::NativeAttributes() {
 | |
|   RefPtr<AccAttributes> attributes = AccessibleWrap::NativeAttributes();
 | |
| 
 | |
|   // 'formatting' attribute is deprecated, 'display' attribute should be
 | |
|   // instead.
 | |
|   nsIFrame* frame = GetFrame();
 | |
|   if (frame && frame->IsBlockFrame()) {
 | |
|     attributes->SetAttribute(nsGkAtoms::formatting, nsGkAtoms::block);
 | |
|   }
 | |
| 
 | |
|   if (FocusMgr()->IsFocused(this)) {
 | |
|     int32_t lineNumber = CaretLineNumber();
 | |
|     if (lineNumber >= 1) {
 | |
|       attributes->SetAttribute(nsGkAtoms::lineNumber, lineNumber);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (HasOwnContent()) {
 | |
|     GetAccService()->MarkupAttributes(this, attributes);
 | |
|     if (mContent->IsMathMLElement()) SetMathMLXMLRoles(attributes);
 | |
|   }
 | |
| 
 | |
|   return attributes.forget();
 | |
| }
 | |
| 
 | |
| int32_t HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY,
 | |
|                                            uint32_t aCoordType) {
 | |
|   nsIFrame* hyperFrame = GetFrame();
 | |
|   if (!hyperFrame) return -1;
 | |
| 
 | |
|   LayoutDeviceIntPoint coords =
 | |
|       nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, this);
 | |
| 
 | |
|   nsPresContext* presContext = mDoc->PresContext();
 | |
|   nsPoint coordsInAppUnits = LayoutDeviceIntPoint::ToAppUnits(
 | |
|       coords, presContext->AppUnitsPerDevPixel());
 | |
| 
 | |
|   nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
 | |
|   if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y)) {
 | |
|     return -1;  // Not found
 | |
|   }
 | |
| 
 | |
|   nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.X(),
 | |
|                            coordsInAppUnits.y - frameScreenRect.Y());
 | |
| 
 | |
|   // Go through the frames to check if each one has the point.
 | |
|   // When one does, add up the character offsets until we have a match
 | |
| 
 | |
|   // We have an point in an accessible child of this, now we need to add up the
 | |
|   // offsets before it to what we already have
 | |
|   int32_t offset = 0;
 | |
|   uint32_t childCount = ChildCount();
 | |
|   for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
 | |
|     LocalAccessible* childAcc = mChildren[childIdx];
 | |
| 
 | |
|     nsIFrame* primaryFrame = childAcc->GetFrame();
 | |
|     NS_ENSURE_TRUE(primaryFrame, -1);
 | |
| 
 | |
|     nsIFrame* frame = primaryFrame;
 | |
|     while (frame) {
 | |
|       nsIContent* content = frame->GetContent();
 | |
|       NS_ENSURE_TRUE(content, -1);
 | |
|       nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
 | |
|       nsSize frameSize = frame->GetSize();
 | |
|       if (pointInFrame.x < frameSize.width &&
 | |
|           pointInFrame.y < frameSize.height) {
 | |
|         // Finished
 | |
|         if (frame->IsTextFrame()) {
 | |
|           nsIFrame::ContentOffsets contentOffsets =
 | |
|               frame->GetContentOffsetsFromPointExternal(
 | |
|                   pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
 | |
|           if (contentOffsets.IsNull() || contentOffsets.content != content) {
 | |
|             return -1;  // Not found
 | |
|           }
 | |
|           uint32_t addToOffset;
 | |
|           nsresult rv = ContentToRenderedOffset(
 | |
|               primaryFrame, contentOffsets.offset, &addToOffset);
 | |
|           NS_ENSURE_SUCCESS(rv, -1);
 | |
|           offset += addToOffset;
 | |
|         }
 | |
|         return offset;
 | |
|       }
 | |
|       frame = frame->GetNextContinuation();
 | |
|     }
 | |
| 
 | |
|     offset += nsAccUtils::TextLength(childAcc);
 | |
|   }
 | |
| 
 | |
|   return -1;  // Not found
 | |
| }
 | |
| 
 | |
| already_AddRefed<EditorBase> HyperTextAccessible::GetEditor() const {
 | |
|   if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
 | |
|     // If we're inside an editable container, then return that container's
 | |
|     // editor
 | |
|     LocalAccessible* ancestor = LocalParent();
 | |
|     while (ancestor) {
 | |
|       HyperTextAccessible* hyperText = ancestor->AsHyperText();
 | |
|       if (hyperText) {
 | |
|         // Recursion will stop at container doc because it has its own impl
 | |
|         // of GetEditor()
 | |
|         return hyperText->GetEditor();
 | |
|       }
 | |
| 
 | |
|       ancestor = ancestor->LocalParent();
 | |
|     }
 | |
| 
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
 | |
|   nsCOMPtr<nsIEditingSession> editingSession;
 | |
|   docShell->GetEditingSession(getter_AddRefs(editingSession));
 | |
|   if (!editingSession) return nullptr;  // No editing session interface
 | |
| 
 | |
|   dom::Document* docNode = mDoc->DocumentNode();
 | |
|   RefPtr<HTMLEditor> htmlEditor =
 | |
|       editingSession->GetHTMLEditorForWindow(docNode->GetWindow());
 | |
|   return htmlEditor.forget();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * =================== Caret & Selection ======================
 | |
|  */
 | |
| 
 | |
| nsresult HyperTextAccessible::SetSelectionRange(int32_t aStartPos,
 | |
|                                                 int32_t aEndPos) {
 | |
|   // Before setting the selection range, we need to ensure that the editor
 | |
|   // is initialized. (See bug 804927.)
 | |
|   // Otherwise, it's possible that lazy editor initialization will override
 | |
|   // the selection we set here and leave the caret at the end of the text.
 | |
|   // By calling GetEditor here, we ensure that editor initialization is
 | |
|   // completed before we set the selection.
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
| 
 | |
|   bool isFocusable = InteractiveState() & states::FOCUSABLE;
 | |
| 
 | |
|   // If accessible is focusable then focus it before setting the selection to
 | |
|   // neglect control's selection changes on focus if any (for example, inputs
 | |
|   // that do select all on focus).
 | |
|   // some input controls
 | |
|   if (isFocusable) TakeFocus();
 | |
| 
 | |
|   RefPtr<dom::Selection> domSel = DOMSelection();
 | |
|   NS_ENSURE_STATE(domSel);
 | |
| 
 | |
|   // Set up the selection.
 | |
|   domSel->RemoveAllRanges(IgnoreErrors());
 | |
|   SetSelectionBoundsAt(0, aStartPos, aEndPos);
 | |
| 
 | |
|   // Make sure it is visible
 | |
|   domSel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
 | |
|                          ScrollAxis(), ScrollAxis(),
 | |
|                          dom::Selection::SCROLL_FOR_CARET_MOVE |
 | |
|                              dom::Selection::SCROLL_OVERFLOW_HIDDEN);
 | |
| 
 | |
|   // When selection is done, move the focus to the selection if accessible is
 | |
|   // not focusable. That happens when selection is set within hypertext
 | |
|   // accessible.
 | |
|   if (isFocusable) return NS_OK;
 | |
| 
 | |
|   nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
 | |
|   if (DOMFocusManager) {
 | |
|     NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
 | |
|     dom::Document* docNode = mDoc->DocumentNode();
 | |
|     NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
 | |
|     nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow();
 | |
|     RefPtr<dom::Element> result;
 | |
|     DOMFocusManager->MoveFocus(
 | |
|         window, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
 | |
|         nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result));
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| int32_t HyperTextAccessible::CaretOffset() const {
 | |
|   // Not focused focusable accessible except document accessible doesn't have
 | |
|   // a caret.
 | |
|   if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
 | |
|       (InteractiveState() & states::FOCUSABLE)) {
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   // Check cached value.
 | |
|   int32_t caretOffset = -1;
 | |
|   HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset);
 | |
| 
 | |
|   // Use cached value if it corresponds to this accessible.
 | |
|   if (caretOffset != -1) {
 | |
|     if (text == this) return caretOffset;
 | |
| 
 | |
|     nsINode* textNode = text->GetNode();
 | |
|     // Ignore offset if cached accessible isn't a text leaf.
 | |
|     if (nsCoreUtils::IsAncestorOf(GetNode(), textNode)) {
 | |
|       return TransformOffset(text, textNode->IsText() ? caretOffset : 0, false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // No caret if the focused node is not inside this DOM node and this DOM node
 | |
|   // is not inside of focused node.
 | |
|   FocusManager::FocusDisposition focusDisp =
 | |
|       FocusMgr()->IsInOrContainsFocus(this);
 | |
|   if (focusDisp == FocusManager::eNone) return -1;
 | |
| 
 | |
|   // Turn the focus node and offset of the selection into caret hypretext
 | |
|   // offset.
 | |
|   dom::Selection* domSel = DOMSelection();
 | |
|   NS_ENSURE_TRUE(domSel, -1);
 | |
| 
 | |
|   nsINode* focusNode = domSel->GetFocusNode();
 | |
|   uint32_t focusOffset = domSel->FocusOffset();
 | |
| 
 | |
|   // No caret if this DOM node is inside of focused node but the selection's
 | |
|   // focus point is not inside of this DOM node.
 | |
|   if (focusDisp == FocusManager::eContainedByFocus) {
 | |
|     nsINode* resultNode =
 | |
|         nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
 | |
| 
 | |
|     nsINode* thisNode = GetNode();
 | |
|     if (resultNode != thisNode &&
 | |
|         !nsCoreUtils::IsAncestorOf(thisNode, resultNode)) {
 | |
|       return -1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return DOMPointToOffset(focusNode, focusOffset);
 | |
| }
 | |
| 
 | |
| int32_t HyperTextAccessible::CaretLineNumber() {
 | |
|   // Provide the line number for the caret, relative to the
 | |
|   // currently focused node. Use a 1-based index
 | |
|   RefPtr<nsFrameSelection> frameSelection = FrameSelection();
 | |
|   if (!frameSelection) return -1;
 | |
| 
 | |
|   dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal);
 | |
|   if (!domSel) return -1;
 | |
| 
 | |
|   nsINode* caretNode = domSel->GetFocusNode();
 | |
|   if (!caretNode || !caretNode->IsContent()) return -1;
 | |
| 
 | |
|   nsIContent* caretContent = caretNode->AsContent();
 | |
|   if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) return -1;
 | |
| 
 | |
|   uint32_t caretOffset = domSel->FocusOffset();
 | |
|   CaretAssociationHint hint = frameSelection->GetHint();
 | |
|   nsIFrame* caretFrame = SelectionMovementUtils::GetFrameForNodeOffset(
 | |
|       caretContent, caretOffset, hint);
 | |
|   NS_ENSURE_TRUE(caretFrame, -1);
 | |
| 
 | |
|   AutoAssertNoDomMutations guard;  // The nsILineIterators below will break if
 | |
|                                    // the DOM is modified while they're in use!
 | |
|   int32_t lineNumber = 1;
 | |
|   nsILineIterator* lineIterForCaret = nullptr;
 | |
|   nsIContent* hyperTextContent = IsContent() ? mContent.get() : nullptr;
 | |
|   while (caretFrame) {
 | |
|     if (hyperTextContent == caretFrame->GetContent()) {
 | |
|       return lineNumber;  // Must be in a single line hyper text, there is no
 | |
|                           // line iterator
 | |
|     }
 | |
|     nsContainerFrame* parentFrame = caretFrame->GetParent();
 | |
|     if (!parentFrame) break;
 | |
| 
 | |
|     // Add lines for the sibling frames before the caret
 | |
|     nsIFrame* sibling = parentFrame->PrincipalChildList().FirstChild();
 | |
|     while (sibling && sibling != caretFrame) {
 | |
|       nsILineIterator* lineIterForSibling = sibling->GetLineIterator();
 | |
|       if (lineIterForSibling) {
 | |
|         // For the frames before that grab all the lines
 | |
|         int32_t addLines = lineIterForSibling->GetNumLines();
 | |
|         lineNumber += addLines;
 | |
|       }
 | |
|       sibling = sibling->GetNextSibling();
 | |
|     }
 | |
| 
 | |
|     // Get the line number relative to the container with lines
 | |
|     if (!lineIterForCaret) {  // Add the caret line just once
 | |
|       lineIterForCaret = parentFrame->GetLineIterator();
 | |
|       if (lineIterForCaret) {
 | |
|         // Ancestor of caret
 | |
|         int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame);
 | |
|         lineNumber += addLines;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     caretFrame = parentFrame;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT_UNREACHABLE(
 | |
|       "DOM ancestry had this hypertext but frame ancestry didn't");
 | |
|   return lineNumber;
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntRect HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) {
 | |
|   *aWidget = nullptr;
 | |
| 
 | |
|   RefPtr<nsCaret> caret = mDoc->PresShellPtr()->GetCaret();
 | |
|   NS_ENSURE_TRUE(caret, LayoutDeviceIntRect());
 | |
| 
 | |
|   bool isVisible = caret->IsVisible();
 | |
|   if (!isVisible) return LayoutDeviceIntRect();
 | |
| 
 | |
|   nsRect rect;
 | |
|   nsIFrame* frame = caret->GetGeometry(&rect);
 | |
|   if (!frame || rect.IsEmpty()) return LayoutDeviceIntRect();
 | |
| 
 | |
|   PresShell* presShell = mDoc->PresShellPtr();
 | |
|   // Transform rect to be relative to the root frame.
 | |
|   nsIFrame* rootFrame = presShell->GetRootFrame();
 | |
|   rect = nsLayoutUtils::TransformFrameRectToAncestor(frame, rect, rootFrame);
 | |
|   // We need to inverse translate with the offset of the edge of the visual
 | |
|   // viewport from top edge of the layout viewport.
 | |
|   nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
 | |
|                            presShell->GetLayoutViewportOffset();
 | |
|   rect.MoveBy(-viewportOffset);
 | |
|   // We need to take into account a non-1 resolution set on the presshell.
 | |
|   // This happens with async pinch zooming. Here we scale the bounds before
 | |
|   // adding the screen-relative offset.
 | |
|   rect.ScaleRoundOut(presShell->GetResolution());
 | |
|   // Now we need to put the rect in absolute screen coords.
 | |
|   nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
 | |
|   rect.MoveBy(rootScreenRect.TopLeft());
 | |
|   // Finally, convert from app units.
 | |
|   auto caretRect = LayoutDeviceIntRect::FromAppUnitsToNearest(
 | |
|       rect, presShell->GetPresContext()->AppUnitsPerDevPixel());
 | |
| 
 | |
|   // Correct for character size, so that caret always matches the size of
 | |
|   // the character. This is important for font size transitions, and is
 | |
|   // necessary because the Gecko caret uses the previous character's size as
 | |
|   // the user moves forward in the text by character.
 | |
|   int32_t caretOffset = CaretOffset();
 | |
|   if (NS_WARN_IF(caretOffset == -1)) {
 | |
|     // The caret offset will be -1 if this Accessible isn't focused. Note that
 | |
|     // the DOM node contaning the caret might be focused, but the Accessible
 | |
|     // might not be; e.g. due to an autocomplete popup suggestion having a11y
 | |
|     // focus.
 | |
|     return LayoutDeviceIntRect();
 | |
|   }
 | |
|   LayoutDeviceIntRect charRect = CharBounds(
 | |
|       caretOffset, nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
 | |
|   if (!charRect.IsEmpty()) {
 | |
|     caretRect.SetTopEdge(charRect.Y());
 | |
|   }
 | |
| 
 | |
|   *aWidget = frame->GetNearestWidget();
 | |
|   return caretRect;
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType,
 | |
|                                                 nsTArray<nsRange*>* aRanges) {
 | |
|   if (IsDoc() && !AsDoc()->HasLoadState(DocAccessible::eTreeConstructed)) {
 | |
|     // Rarely, a client query can be handled after a DocAccessible is created
 | |
|     // but before the initial tree is constructed, since DoInitialUpdate happens
 | |
|     // during a refresh tick. In that case, there might be a DOM selection, but
 | |
|     // we can't use it. We will crash if we try due to mContent being null, etc.
 | |
|     // This should only happen in the parent process because we should never
 | |
|     // try to push the cache in a content process before the initial tree is
 | |
|     // constructed.
 | |
|     MOZ_ASSERT(XRE_IsParentProcess(), "Query before DoInitialUpdate");
 | |
|     return;
 | |
|   }
 | |
|   // Ignore selection if it is not visible.
 | |
|   RefPtr<nsFrameSelection> frameSelection = FrameSelection();
 | |
|   if (!frameSelection || frameSelection->GetDisplaySelection() <=
 | |
|                              nsISelectionController::SELECTION_HIDDEN) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   dom::Selection* domSel = frameSelection->GetSelection(aSelectionType);
 | |
|   if (!domSel) return;
 | |
| 
 | |
|   nsINode* startNode = GetNode();
 | |
| 
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (editorBase) {
 | |
|     startNode = editorBase->GetRoot();
 | |
|   }
 | |
| 
 | |
|   if (!startNode) return;
 | |
| 
 | |
|   uint32_t childCount = startNode->GetChildCount();
 | |
|   nsresult rv = domSel->GetDynamicRangesForIntervalArray(
 | |
|       startNode, 0, startNode, childCount, true, aRanges);
 | |
|   NS_ENSURE_SUCCESS_VOID(rv);
 | |
| 
 | |
|   // Remove collapsed ranges
 | |
|   aRanges->RemoveElementsBy(
 | |
|       [](const auto& range) { return range->Collapsed(); });
 | |
| }
 | |
| 
 | |
| int32_t HyperTextAccessible::SelectionCount() {
 | |
|   nsTArray<nsRange*> ranges;
 | |
|   GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
 | |
|   return ranges.Length();
 | |
| }
 | |
| 
 | |
| bool HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
 | |
|                                             int32_t* aStartOffset,
 | |
|                                             int32_t* aEndOffset) {
 | |
|   *aStartOffset = *aEndOffset = 0;
 | |
| 
 | |
|   nsTArray<nsRange*> ranges;
 | |
|   GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
 | |
| 
 | |
|   uint32_t rangeCount = ranges.Length();
 | |
|   if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsRange* range = ranges[aSelectionNum];
 | |
| 
 | |
|   // Get start and end points.
 | |
|   nsINode* startNode = range->GetStartContainer();
 | |
|   nsINode* endNode = range->GetEndContainer();
 | |
|   uint32_t startOffset = range->StartOffset();
 | |
|   uint32_t endOffset = range->EndOffset();
 | |
| 
 | |
|   // Make sure start is before end, by swapping DOM points.  This occurs when
 | |
|   // the user selects backwards in the text.
 | |
|   const Maybe<int32_t> order =
 | |
|       nsContentUtils::ComparePoints(endNode, endOffset, startNode, startOffset);
 | |
| 
 | |
|   if (!order) {
 | |
|     MOZ_ASSERT_UNREACHABLE();
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (*order < 0) {
 | |
|     std::swap(startNode, endNode);
 | |
|     std::swap(startOffset, endOffset);
 | |
|   }
 | |
| 
 | |
|   if (!startNode->IsInclusiveDescendantOf(mContent)) {
 | |
|     *aStartOffset = 0;
 | |
|   } else {
 | |
|     *aStartOffset =
 | |
|         DOMPointToOffset(startNode, AssertedCast<int32_t>(startOffset));
 | |
|   }
 | |
| 
 | |
|   if (!endNode->IsInclusiveDescendantOf(mContent)) {
 | |
|     *aEndOffset = CharacterCount();
 | |
|   } else {
 | |
|     *aEndOffset =
 | |
|         DOMPointToOffset(endNode, AssertedCast<int32_t>(endOffset), true);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum) {
 | |
|   RefPtr<dom::Selection> domSel = DOMSelection();
 | |
|   if (!domSel) return false;
 | |
| 
 | |
|   if (aSelectionNum < 0 ||
 | |
|       aSelectionNum >= static_cast<int32_t>(domSel->RangeCount())) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const RefPtr<nsRange> range{
 | |
|       domSel->GetRangeAt(static_cast<uint32_t>(aSelectionNum))};
 | |
|   domSel->RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
 | |
|                                                          IgnoreErrors());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
 | |
|                                                  int32_t aEndOffset,
 | |
|                                                  uint32_t aCoordinateType,
 | |
|                                                  int32_t aX, int32_t aY) {
 | |
|   nsIFrame* frame = GetFrame();
 | |
|   if (!frame) return;
 | |
| 
 | |
|   LayoutDeviceIntPoint coords =
 | |
|       nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
 | |
| 
 | |
|   RefPtr<nsRange> domRange = nsRange::Create(mContent);
 | |
|   TextRange range(this, this, aStartOffset, this, aEndOffset);
 | |
|   if (!range.AssignDOMRange(domRange)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsPresContext* presContext = frame->PresContext();
 | |
|   nsPoint coordsInAppUnits = LayoutDeviceIntPoint::ToAppUnits(
 | |
|       coords, presContext->AppUnitsPerDevPixel());
 | |
| 
 | |
|   bool initialScrolled = false;
 | |
|   nsIFrame* parentFrame = frame;
 | |
|   while ((parentFrame = parentFrame->GetParent())) {
 | |
|     nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame);
 | |
|     if (scrollableFrame) {
 | |
|       if (!initialScrolled) {
 | |
|         // Scroll substring to the given point. Turn the point into percents
 | |
|         // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
 | |
|         nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
 | |
|         nscoord offsetPointX = coordsInAppUnits.x - frameRect.X();
 | |
|         nscoord offsetPointY = coordsInAppUnits.y - frameRect.Y();
 | |
| 
 | |
|         nsSize size(parentFrame->GetSize());
 | |
| 
 | |
|         // avoid divide by zero
 | |
|         size.width = size.width ? size.width : 1;
 | |
|         size.height = size.height ? size.height : 1;
 | |
| 
 | |
|         int16_t hPercent = offsetPointX * 100 / size.width;
 | |
|         int16_t vPercent = offsetPointY * 100 / size.height;
 | |
| 
 | |
|         nsresult rv = nsCoreUtils::ScrollSubstringTo(
 | |
|             frame, domRange,
 | |
|             ScrollAxis(WhereToScroll(vPercent), WhenToScroll::Always),
 | |
|             ScrollAxis(WhereToScroll(hPercent), WhenToScroll::Always));
 | |
|         if (NS_FAILED(rv)) return;
 | |
| 
 | |
|         initialScrolled = true;
 | |
|       } else {
 | |
|         // Substring was scrolled to the given point already inside its closest
 | |
|         // scrollable area. If there are nested scrollable areas then make
 | |
|         // sure we scroll lower areas to the given point inside currently
 | |
|         // traversed scrollable area.
 | |
|         nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
 | |
|       }
 | |
|     }
 | |
|     frame = parentFrame;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::SelectionRanges(
 | |
|     nsTArray<a11y::TextRange>* aRanges) const {
 | |
|   dom::Selection* sel = DOMSelection();
 | |
|   if (!sel) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   TextRange::TextRangesFromSelection(sel, aRanges);
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::ReplaceText(const nsAString& aText) {
 | |
|   if (aText.Length() == 0) {
 | |
|     DeleteText(0, CharacterCount());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SetSelectionRange(0, CharacterCount());
 | |
| 
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (!editorBase) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DebugOnly<nsresult> rv = editorBase->InsertTextAsAction(aText);
 | |
|   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert the new text");
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::InsertText(const nsAString& aText,
 | |
|                                      int32_t aPosition) {
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (editorBase) {
 | |
|     SetSelectionRange(aPosition, aPosition);
 | |
|     DebugOnly<nsresult> rv = editorBase->InsertTextAsAction(aText);
 | |
|     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert the text");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (editorBase) {
 | |
|     SetSelectionRange(aStartPos, aEndPos);
 | |
|     editorBase->Copy();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (editorBase) {
 | |
|     SetSelectionRange(aStartPos, aEndPos);
 | |
|     editorBase->Cut();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (!editorBase) {
 | |
|     return;
 | |
|   }
 | |
|   SetSelectionRange(aStartPos, aEndPos);
 | |
|   DebugOnly<nsresult> rv =
 | |
|       editorBase->DeleteSelectionAsAction(nsIEditor::eNone, nsIEditor::eStrip);
 | |
|   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to delete text");
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::PasteText(int32_t aPosition) {
 | |
|   RefPtr<EditorBase> editorBase = GetEditor();
 | |
|   if (editorBase) {
 | |
|     SetSelectionRange(aPosition, aPosition);
 | |
|     editorBase->PasteAsAction(nsIClipboard::kGlobalClipboard,
 | |
|                               EditorBase::DispatchPasteEvent::Yes);
 | |
|   }
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // LocalAccessible public
 | |
| 
 | |
| // LocalAccessible protected
 | |
| ENameValueFlag HyperTextAccessible::NativeName(nsString& aName) const {
 | |
|   // Check @alt attribute for invalid img elements.
 | |
|   if (mContent->IsHTMLElement(nsGkAtoms::img)) {
 | |
|     mContent->AsElement()->GetAttr(nsGkAtoms::alt, aName);
 | |
|     if (!aName.IsEmpty()) return eNameOK;
 | |
|   }
 | |
| 
 | |
|   ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
 | |
|   if (!aName.IsEmpty()) return nameFlag;
 | |
| 
 | |
|   // Get name from title attribute for HTML abbr and acronym elements making it
 | |
|   // a valid name from markup. Otherwise their name isn't picked up by recursive
 | |
|   // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
 | |
|   if (IsAbbreviation() && mContent->AsElement()->GetAttr(
 | |
|                               kNameSpaceID_None, nsGkAtoms::title, aName)) {
 | |
|     aName.CompressWhitespace();
 | |
|   }
 | |
| 
 | |
|   return eNameOK;
 | |
| }
 | |
| 
 | |
| void HyperTextAccessible::Shutdown() {
 | |
|   mOffsets.Clear();
 | |
|   AccessibleWrap::Shutdown();
 | |
| }
 | |
| 
 | |
| bool HyperTextAccessible::RemoveChild(LocalAccessible* aAccessible) {
 | |
|   const int32_t childIndex = aAccessible->IndexInParent();
 | |
|   if (childIndex < static_cast<int32_t>(mOffsets.Length())) {
 | |
|     mOffsets.RemoveLastElements(mOffsets.Length() - childIndex);
 | |
|   }
 | |
| 
 | |
|   return AccessibleWrap::RemoveChild(aAccessible);
 | |
| }
 | |
| 
 | |
| bool HyperTextAccessible::InsertChildAt(uint32_t aIndex,
 | |
|                                         LocalAccessible* aChild) {
 | |
|   if (aIndex < mOffsets.Length()) {
 | |
|     mOffsets.RemoveLastElements(mOffsets.Length() - aIndex);
 | |
|   }
 | |
| 
 | |
|   return AccessibleWrap::InsertChildAt(aIndex, aChild);
 | |
| }
 | |
| 
 | |
| Relation HyperTextAccessible::RelationByType(RelationType aType) const {
 | |
|   Relation rel = LocalAccessible::RelationByType(aType);
 | |
| 
 | |
|   switch (aType) {
 | |
|     case RelationType::NODE_CHILD_OF:
 | |
|       if (HasOwnContent() && mContent->IsMathMLElement()) {
 | |
|         LocalAccessible* parent = LocalParent();
 | |
|         if (parent) {
 | |
|           nsIContent* parentContent = parent->GetContent();
 | |
|           if (parentContent &&
 | |
|               parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
 | |
|             // Add a relation pointing to the parent <mroot>.
 | |
|             rel.AppendTarget(parent);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     case RelationType::NODE_PARENT_OF:
 | |
|       if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
 | |
|         LocalAccessible* base = LocalChildAt(0);
 | |
|         LocalAccessible* index = LocalChildAt(1);
 | |
|         if (base && index) {
 | |
|           // Append the <mroot> children in the order index, base.
 | |
|           rel.AppendTarget(index);
 | |
|           rel.AppendTarget(base);
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return rel;
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // HyperTextAccessible public static
 | |
| 
 | |
| nsresult HyperTextAccessible::ContentToRenderedOffset(
 | |
|     nsIFrame* aFrame, int32_t aContentOffset, uint32_t* aRenderedOffset) const {
 | |
|   if (!aFrame) {
 | |
|     // Current frame not rendered -- this can happen if text is set on
 | |
|     // something with display: none
 | |
|     *aRenderedOffset = 0;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (IsTextField()) {
 | |
|     *aRenderedOffset = aContentOffset;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   NS_ASSERTION(aFrame->IsTextFrame(), "Need text frame for offset conversion");
 | |
|   NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
 | |
|                "Call on primary frame only");
 | |
| 
 | |
|   nsIFrame::RenderedText text =
 | |
|       aFrame->GetRenderedText(aContentOffset, aContentOffset + 1,
 | |
|                               nsIFrame::TextOffsetType::OffsetsInContentText,
 | |
|                               nsIFrame::TrailingWhitespace::DontTrim);
 | |
|   *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult HyperTextAccessible::RenderedToContentOffset(
 | |
|     nsIFrame* aFrame, uint32_t aRenderedOffset, int32_t* aContentOffset) const {
 | |
|   if (IsTextField()) {
 | |
|     *aContentOffset = aRenderedOffset;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   *aContentOffset = 0;
 | |
|   NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
 | |
| 
 | |
|   NS_ASSERTION(aFrame->IsTextFrame(), "Need text frame for offset conversion");
 | |
|   NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
 | |
|                "Call on primary frame only");
 | |
| 
 | |
|   nsIFrame::RenderedText text =
 | |
|       aFrame->GetRenderedText(aRenderedOffset, aRenderedOffset + 1,
 | |
|                               nsIFrame::TextOffsetType::OffsetsInRenderedText,
 | |
|                               nsIFrame::TrailingWhitespace::DontTrim);
 | |
|   *aContentOffset = text.mOffsetWithinNodeText;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 |