Commit f2af766c authored by aaronleventhal%moonset.net's avatar aaronleventhal%moonset.net
Browse files

Bug 345825. Fixes for accessible text methods getTextAtOffset and getRangeExtents. r=ginn.chen

parent a2af6643
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ typedef long nsAccessibleCoordType;

interface nsIAccessible;

[scriptable, uuid(E44D3FA6-9CB2-432A-8BDB-69D72B6ADA00)]
[scriptable, uuid(c3a9526c-4834-4237-822c-c02ef5b6b8b1)]
interface nsIAccessibleText : nsISupports
{
  const nsAccessibleTextBoundary BOUNDARY_CHAR = 0;
@@ -111,6 +111,14 @@ interface nsIAccessibleText : nsISupports
                            out long height,
                            in nsAccessibleCoordType coordType);

  void getRangeExtents (in long startOffset,
                        in long endOffset,
                        out long x,
                        out long y,
                        out long width,
                        out long height,
                        in nsAccessibleCoordType coordType);

  long getOffsetAtPoint (in long x, in long y,
                         in nsAccessibleCoordType coordType);

+6 −0
Original line number Diff line number Diff line
@@ -69,6 +69,12 @@
#define USE_ATK_EMBED_RELATIONS
#endif

// ATK 1.3.0 or later
#if ATK_MAJOR_VERSION >=2 || \
    (ATK_MAJOR_VERSION == 1 && ATK_MINOR_VERSION >= 3)
#define USE_ATK_GET_RANGE_EXTENTS
#endif

// ATK 1.3.3 or later
#if ATK_MAJOR_VERSION >=2 || \
    (ATK_MAJOR_VERSION == 1 && ATK_MINOR_VERSION >= 4) || \
+34 −1
Original line number Diff line number Diff line
@@ -58,6 +58,9 @@ textInterfaceInitCB(AtkTextIface *aIface)
    aIface->get_run_attributes = getRunAttributesCB;
    aIface->get_default_attributes = getDefaultAttributesCB;
    aIface->get_character_extents = getCharacterExtentsCB;
#ifdef USE_ATK_GET_RANGE_EXTENTS
    aIface->get_range_extents = getRangeExtentsCB;
#endif
    aIface->get_character_count = getCharacterCountCB;
    aIface->get_offset_at_point = getOffsetAtPointCB;
    aIface->get_n_selections = getTextSelectionCountCB;
@@ -272,7 +275,7 @@ getCharacterExtentsCB(AtkText *aText, gint aOffset,
                      AtkCoordType aCoords)
{
    nsAccessibleWrap *accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
    if(!accWrap)
    if(!accWrap || !aX || !aY || !aWidth || !aHeight)
        return;

    nsCOMPtr<nsIAccessibleText> accText;
@@ -294,6 +297,36 @@ getCharacterExtentsCB(AtkText *aText, gint aOffset,
                 "MaiInterfaceText::GetCharacterExtents, failed\n");
}

#ifdef USE_ATK_GET_RANGE_EXTENTS
void
getRangeExtentsCB(AtkText *aText, gint aStartOffset, gint aEndOffset,
                  AtkCoordType aCoords, AtkTextRectangle *aRect)
{
    nsAccessibleWrap *accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
    if(!accWrap || !aRect)
        return;

    nsCOMPtr<nsIAccessibleText> accText;
    accWrap->QueryInterface(NS_GET_IID(nsIAccessibleText),
                            getter_AddRefs(accText));
    if (!accText)
        return;

    PRInt32 extY = 0, extX = 0;
    PRInt32 extWidth = 0, extHeight = 0;
    nsresult rv = accText->GetRangeExtents(aStartOffset, aEndOffset,
                                           &extX, &extY,
                                           &extWidth, &extHeight,
                                           aCoords);
    aRect->x = extX;
    aRect->y = extY;
    aRect->width = extWidth;
    aRect->height = extHeight;
    NS_ASSERTION(NS_SUCCEEDED(rv),
                 "MaiInterfaceText::GetRangeExtents, failed\n");
}
#endif

gint
getCharacterCountCB(AtkText *aText)
{
+5 −0
Original line number Diff line number Diff line
@@ -70,6 +70,11 @@ void getCharacterExtentsCB(AtkText *aText, gint aOffset,
                           gint *aX, gint *aY,
                           gint *aWidth, gint *aHeight,
                           AtkCoordType aCoords);
#ifdef USE_ATK_GET_RANGE_EXTENTS
void getRangeExtentsCB(AtkText *aText, gint aStartOffset,
                       gint aEndOffset, AtkCoordType aCoords,
                       AtkTextRectangle *aRect);
#endif
gint getCharacterCountCB(AtkText *aText);
gint getOffsetAtPointCB(AtkText *aText,
                        gint aX, gint aY,
+190 −101
Original line number Diff line number Diff line
@@ -194,14 +194,79 @@ NS_IMETHODIMP nsHyperTextAccessible::GetExtState(PRUint32 *aExtState)
  return rv;
}

// Substring must be entirely within the same text node
nsIntRect nsHyperTextAccessible::GetBoundsForString(nsIFrame *aFrame, PRInt32 aStartOffset, PRInt32 aLength)
{
  nsIntRect screenRect;
  nsIFrame *frame;
  PRInt32 startOffsetInFrame;
  nsresult rv = aFrame->GetChildFrameContainingOffset(aStartOffset, PR_FALSE,
                                                      &startOffsetInFrame, &frame);
  NS_ENSURE_SUCCESS(rv, screenRect);

  nsIPresShell *shell = frame->GetPresContext()->GetPresShell();
  NS_ENSURE_TRUE(shell, screenRect);

  nsCOMPtr<nsIRenderingContext> rc;
  shell->CreateRenderingContext(frame, getter_AddRefs(rc));
  NS_ENSURE_TRUE(rc, screenRect);

  const nsStyleFont *font = frame->GetStyleFont();
  const nsStyleVisibility *visibility = frame->GetStyleVisibility();

  rv = rc->SetFont(font->mFont, visibility->mLangGroup);
  NS_ENSURE_SUCCESS(rv, screenRect);

  nsPresContext *context = shell->GetPresContext();
  float t2p = context->TwipsToPixels();

  while (frame && aLength > 0) {
    // Start with this frame's screen rect, which we will 
    // shrink based on the substring we care about within it.
    // We will then add that frame to the total screenRect we
    // are returning.
    nsIntRect frameScreenRect = frame->GetScreenRectExternal();

    // Get the length of the substring in this frame that we want the bounds for
    PRInt32 startFrameTextOffset, endFrameTextOffset;
    frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
    PRInt32 frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
    PRInt32 frameSubStringLength = PR_MIN(frameTotalTextLength - startOffsetInFrame, aLength);

    // Add the point where the string starts to the frameScreenRect
    nsPoint frameTextStartPoint;
    rv = frame->GetPointFromOffset(context, rc, aStartOffset, &frameTextStartPoint);
    NS_ENSURE_SUCCESS(rv, nsRect());   
    frameScreenRect.x += NSTwipsToIntPixels(frameTextStartPoint.x, t2p);

    // Use the point for the end offset to calculate the width
    nsPoint frameTextEndPoint;
    rv = frame->GetPointFromOffset(context, rc, aStartOffset + frameSubStringLength, &frameTextEndPoint);
    NS_ENSURE_SUCCESS(rv, nsRect());   
    frameScreenRect.width = NSTwipsToIntPixels(frameTextEndPoint.x - frameTextStartPoint.x, t2p);

    screenRect.UnionRect(frameScreenRect, screenRect);

    // Get ready to loop back for next frame continuation
    aStartOffset += frameSubStringLength;
    startOffsetInFrame = 0;
    aLength -= frameSubStringLength;
    frame = frame->GetNextContinuation();
  }

  return screenRect;
}

/*
 * Gets the specified text.
 */
nsIFrame* nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& aEndOffset, nsAString *aText, nsIFrame **aEndFrame)
nsIFrame* nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& aEndOffset, nsAString *aText,
                                               nsIFrame **aEndFrame, nsIntRect *aBoundsRect)
{
  PRInt32 startOffset = aStartOffset;
  PRInt32 endOffset = aEndOffset;

  // Clear out parameters and set up loop
  if (aText) {
    aText->Truncate();
  }
@@ -217,8 +282,15 @@ nsIFrame* nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& a
  if (aEndFrame) {
    *aEndFrame = nsnull;
  }
  if (aBoundsRect) {
    aBoundsRect->Empty();
  }

  nsIntRect unionRect;
  nsCOMPtr<nsIAccessible> accessible;

  // Loop through children and collect valid offsets, text and bounds
  // depending on what we need for out parameters
  while (NextChild(accessible)) {
    nsCOMPtr<nsPIAccessNode> accessNode(do_QueryInterface(accessible));
    nsIFrame *frame = accessNode->GetFrame();
@@ -259,6 +331,10 @@ nsIFrame* nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& a
          }
          *aText += newText;
        }
        if (aBoundsRect) {
          aBoundsRect->UnionRect(*aBoundsRect, GetBoundsForString(frame, startOffset,
                                                                  substringEndOffset - startOffset));
        }
        if (!startFrame) {
          startFrame = frame;
          aStartOffset = startOffset;
@@ -277,10 +353,15 @@ nsIFrame* nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& a
        -- startOffset;
      }
      else {
        if (aText && endOffset > 0) {
        if (endOffset > 0) {
          if (aText) {
            *aText += (frame->GetType() == nsAccessibilityAtoms::brFrame) ?
                      kForcedNewLineChar : kEmbeddedObjectChar;
          }
          if (aBoundsRect) {
            aBoundsRect->UnionRect(*aBoundsRect, frame->GetScreenRectExternal());
          }
        }
        if (!startFrame) {
          startFrame = frame;
          aStartOffset = 0;
@@ -288,8 +369,8 @@ nsIFrame* nsHyperTextAccessible::GetPosAndText(PRInt32& aStartOffset, PRInt32& a
      }
      -- endOffset;
    }
    if (!endOffset) {
      break;
    if (endOffset <= 0 && startFrame) {
      break; // If we don't have startFrame yet, get that in next loop iteration
    }
  }

@@ -361,25 +442,40 @@ nsresult nsHyperTextAccessible::DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNod
  aNode->GetNodeType(&nodeType);
  if (nodeType != nsIDOMNode::TEXT_NODE) {
    // If not text, aNodeOffset is a child
    if (aNode != mDOMNode) {
#ifdef DEBUG_A11Y
      // This should only happen in the empty plaintext case, when there is no text child yet
      // XXX What's with this check, the assertion is commented out, why?
      nsCOMPtr<nsIEditor> editor = GetEditor();
      nsCOMPtr<nsIPlaintextEditor> plaintextEditor = do_QueryInterface(editor);
      // NS_ASSERTION(plaintextEditor, "DOM Point is not in this nsHyperTextAccessible");
#endif
      return NS_OK;  // No text content yet, because textfield is blank, offset must be 0
    }
    // aNode != mDOMNode for nsDocAccessible case, where 
    // aNode is for <body> and mDOMNode is doc element
    nsCOMPtr<nsINode> hyperTextNode(do_QueryInterface(aNode));
    NS_ENSURE_TRUE(hyperTextNode, NS_ERROR_FAILURE);
    nsCOMPtr<nsIAccessible> childAccessible;
    // Find first accessible child starting at aNodeOffset. We need to do this because
    // the node at aNodeOffset might not be an accessible
    while (!childAccessible) {
      nsIContent *childContent = hyperTextNode->GetChildAt(aNodeOffset ++);
      nsCOMPtr<nsIDOMNode> childNode(do_QueryInterface(childContent));
      if (!childNode) {
        break; // Caret at end of hyper text
      }
      GetAccService()->GetAccessibleFor(childNode, getter_AddRefs(childAccessible));
    }
    
    // Loop through, adding offsets until we reach childAccessible
    // If childAccessible is null we will end up adding up the entire length of
    // the hypertext, which is good -- it just means our offset node
    // came after the last accessible child's node
    nsCOMPtr<nsIAccessible> accessible;
    while (NextChild(accessible)) {
      if (aNodeOffset -- == 0) {
        nsCOMPtr<nsIAccessibleHyperLink> hyperLink = do_QueryInterface(accessible);
        NS_ENSURE_TRUE(hyperLink, NS_ERROR_FAILURE);
        return hyperLink->GetStartIndex(aResult);
    while (NextChild(accessible) && accessible != childAccessible) {
      if (Role(accessible) == ROLE_TEXT_LEAF) {
        nsCOMPtr<nsPIAccessNode> accessNode(do_QueryInterface(accessible));
        nsIFrame *frame = accessNode->GetFrame();
        if (frame) {
          *aResult += frame->GetContent()->TextLength();
        }
      }
      else {
        ++ *aResult; // Embedded object or <br>
      }
    }
    return NS_ERROR_FAILURE; // Child not found
    return NS_OK;
  }

  // This is text, and aNodeOffset is the nth character in the text
@@ -440,17 +536,14 @@ PRInt32 nsHyperTextAccessible::GetRelativeOffset(nsIPresShell *aPresShell, nsIFr
  }
  nsCOMPtr<nsIDOMNode> resultNode = do_QueryInterface(resultContent);
  PRInt32 hyperTextOffset;
  if (resultContent && NS_SUCCEEDED(DOMPointToOffset(resultNode, resultOffset, &hyperTextOffset))) {
    if (aAmount == eSelectLine && !aNeedsStart && hyperTextOffset > 0) {
      -- hyperTextOffset; // We want the end of the previous line in this case
    }
  }
  else if (aDirection == eDirNext) {
  if (!resultContent || NS_FAILED(DOMPointToOffset(resultNode, resultOffset, &hyperTextOffset))) {
    if (aDirection == eDirNext) {
      GetCharacterCount(&hyperTextOffset);
    }
    else {
      hyperTextOffset = 0;
    }
  }

  return hyperTextOffset;
}
@@ -486,6 +579,14 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
    return NS_ERROR_FAILURE;
  }

  if (aBoundaryType == BOUNDARY_LINE_END) {
      // When our position is right on the line boundary and our
      // boundary type is BOUNDARY_LINE_END, we must start
      // our calculation from the beginning of the next line
      endOffset = startOffset = aOffset + 1;
      startFrame = GetPosAndText(startOffset, endOffset);
  }

  nsSelectionAmount amount;
  PRBool needsStart = PR_FALSE;
  switch (aBoundaryType)
@@ -504,10 +605,15 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
    amount = eSelectWord;
    break;
  case BOUNDARY_LINE_START:
    needsStart = PR_TRUE;
    // Newlines are considered at the end of a line,
    // Since getting the BOUNDARY_LINE_START gets the text from the line-start
    // to the next line-start, the newline is included at the end of the string
    amount = eSelectLine;
    break;
  case BOUNDARY_LINE_END:
    // Newlines are considered at the end of a line,
    // Since getting the BOUNDARY_END_START gets the text from the line-end
    // to the next line-end, the newline is included at the beginning of the string
    amount = eSelectLine;
    break;
  case BOUNDARY_SENTENCE_START:
@@ -537,10 +643,22 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe

  // If aType == eGetAt we'll change both the start and end offset from
  // the original offset
  startOffset = (aType == eGetAfter) ? 
                  aOffset : GetRelativeOffset(presShell, startFrame, 
                              startOffset, amount, eDirPrevious, needsStart);
  *aStartOffset = startOffset;
  PRInt32 startForwardSearchOffset;
  if (aType == eGetAfter) {
    startForwardSearchOffset = startOffset = aOffset;
  }
  else {
    startOffset = GetRelativeOffset(presShell, startFrame,  startOffset + (amount == eSelectLine),
                                    amount, eDirPrevious, needsStart);
    startForwardSearchOffset = startOffset;
    if (amount == eSelectLine) {
      ++ startForwardSearchOffset;
      if (aBoundaryType == BOUNDARY_LINE_END && startOffset > 0) {
        // Start line fixup: include \n at start of string
        -- startOffset;
      }
    }
  }

  if (aType == eGetBefore) {
    endOffset = aOffset;
@@ -548,21 +666,30 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
  else {
    // Start moving forward from the start so that we don't get 
    // 2 words/lines/sentences if the offset occured on whitespace boundary    
    
    // Use temp so that startOffset is not modified
    PRInt32 tempOffset = startOffset;
    if (aBoundaryType == BOUNDARY_LINE_START) {
      ++ tempOffset;
    }
    endOffset = tempOffset;
    nsIFrame *endFrame = GetPosAndText(tempOffset, endOffset);
    endOffset = startForwardSearchOffset; // Passed by reference
    nsIFrame *endFrame = GetPosAndText(startForwardSearchOffset, endOffset);
    if (!endFrame) {
      return NS_ERROR_FAILURE;
    }
    endOffset = GetRelativeOffset(presShell, endFrame, endOffset, amount,
                                  eDirNext, needsStart);
    if (aBoundaryType == BOUNDARY_LINE_START) {
      PRInt32 textLength = (endFrame->GetType() == nsAccessibilityAtoms::textFrame) ?
                           endFrame->GetContent()->TextLength() : 1;
      PRInt32 startFrameOffsetUnused, endFrameOffset;
      endFrame->GetOffsets(startFrameOffsetUnused, endFrameOffset);
      if (textLength == endFrameOffset) {
        nsIFrame *nextSibling = endFrame->GetNextSibling();
        if (nextSibling && nextSibling->GetType() == nsAccessibilityAtoms::brFrame) {
          // End line fixup: include \n for <br> at start of string,
          // just as whitespace would be included if wrapped without <br>
          ++ endOffset;
        }
      }
    }
  }

  *aStartOffset = startOffset;
  *aEndOffset = endOffset;

  return GetPosAndText(startOffset, endOffset, &aText) ? NS_OK : NS_ERROR_FAILURE;
@@ -635,68 +762,32 @@ NS_IMETHODIMP nsHyperTextAccessible::GetCharacterExtents(PRInt32 aOffset, PRInt3
                                                         PRInt32 *aWidth, PRInt32 *aHeight,
                                                         nsAccessibleCoordType aCoordType)
{
  *aX = *aY = *aWidth = *aHeight = 0;
  nsCOMPtr<nsIPresShell> shell = GetPresShell();
  NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);

  nsAutoString firstChar;
  // Will convert aOffset into offset into corresponding descendant frame
  PRInt32 endOffset = aOffset + 1;
  nsIFrame *frame = GetPosAndText(aOffset, endOffset, &firstChar);
  if (!frame) {
    return NS_ERROR_FAILURE;
  return GetRangeExtents(aOffset, aOffset + 1, aX, aY, aWidth, aHeight, aCoordType);
}

  nsIntRect frameScreenRect = frame->GetScreenRectExternal();
  if (firstChar.First() != kEmbeddedObjectChar) {
    nsresult rv = frame->GetChildFrameContainingOffset(aOffset, PR_FALSE,
                                                       &aOffset, &frame);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIRenderingContext> rc;
    shell->CreateRenderingContext(frame, getter_AddRefs(rc));
    NS_ENSURE_TRUE(rc, NS_ERROR_FAILURE);

    const nsStyleFont *font = frame->GetStyleFont();
    const nsStyleVisibility *visibility = frame->GetStyleVisibility();

    rv = rc->SetFont(font->mFont, visibility->mLangGroup);
    NS_ENSURE_SUCCESS(rv, rv);

    nsIFontMetrics *fm;
    rv = rc->GetFontMetrics(fm);
    NS_ENSURE_SUCCESS(rv, rv);

    nsPresContext *context = shell->GetPresContext();
    float t2p = context->TwipsToPixels();

    //Getting width
    nscoord tmpWidth;
    if (NS_SUCCEEDED(rc->GetWidth(firstChar.First(), tmpWidth))) {
      *aWidth = NSTwipsToIntPixels(tmpWidth, t2p);
    }

    //Getting height
    nscoord tmpHeight;
    if (NS_SUCCEEDED(fm->GetHeight(tmpHeight))) {
      *aHeight = NSTwipsToIntPixels(tmpHeight, t2p);
/*
 * Given a start & end offset, the x, y, width, and height values are filled appropriately.
 */
NS_IMETHODIMP nsHyperTextAccessible::GetRangeExtents(PRInt32 aStartOffset, PRInt32 aEndOffset,
                                                     PRInt32 *aX, PRInt32 *aY,
                                                     PRInt32 *aWidth, PRInt32 *aHeight,
                                                     nsAccessibleCoordType aCoordType)
{
  nsIntRect boundsRect;
  nsIFrame *endFrameUnused;
  if (!GetPosAndText(aStartOffset, aEndOffset, nsnull, &endFrameUnused, &boundsRect)) {
    return NS_ERROR_FAILURE;
  }

    //add the width of the string before current char
    nsPoint pt;
    rv = frame->GetPointFromOffset(context, rc, aOffset, &pt);
    NS_ENSURE_SUCCESS(rv, rv);
    frameScreenRect.x += NSTwipsToIntPixels(pt.x, t2p);
  }
  else {
    *aWidth  = frameScreenRect.width;
    *aHeight = frameScreenRect.height;
  }
  *aX = boundsRect.x;
  *aY = boundsRect.y;
  *aWidth = boundsRect.width;
  *aHeight = boundsRect.height;

  *aX = frameScreenRect.x;
  *aY = frameScreenRect.y;
  if (aCoordType == COORD_TYPE_WINDOW) {
    //co-ord type = window
    nsCOMPtr<nsIPresShell> shell = GetPresShell();
    NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
    nsCOMPtr<nsIDocument> doc = shell->GetDocument();
    nsCOMPtr<nsIDOMDocumentView> docView(do_QueryInterface(doc));
    NS_ENSURE_TRUE(docView, NS_ERROR_FAILURE);
@@ -1103,8 +1194,6 @@ NS_IMETHODIMP nsHyperTextAccessible::WillDeleteSelection(nsISelection *aSelectio
// <input> & <textarea> fires this event while deleting text
// <editor> fires WillDeleteText/WillDeleteNode instead
// XXX Deal with > 1 selections
// XXX selectionStart is too large for backspace presses, it should be 1 smaller,
//     however that must be fixed in the editor code
{
  PRInt32 selectionStart, selectionEnd;
  GetSelectionBounds(0, &selectionStart, &selectionEnd);
Loading