diff --git a/accessible/public/nsIAccessibleText.idl b/accessible/public/nsIAccessibleText.idl index 9cb75f27ba8..da1054d420d 100644 --- a/accessible/public/nsIAccessibleText.idl +++ b/accessible/public/nsIAccessibleText.idl @@ -44,8 +44,9 @@ typedef long nsAccessibleTextBoundary; interface nsIAccessible; +interface nsIPersistentProperties; -[scriptable, uuid(caa4f543-070e-4705-8428-2e53575c41bb)] +[scriptable, uuid(0f4633b1-550c-4b50-8c04-0eb1005eef2f)] interface nsIAccessibleText : nsISupports { // In parameters for character offsets: @@ -102,13 +103,24 @@ interface nsIAccessibleText : nsISupports wchar getCharacterAtOffset (in long offset); /** - * Get the accessible and start/end offsets around the given offset. - * This accessible get return the DOM node and layout frame - * with the uniform attributes for this range of text + * Get the accessible start/end offsets around the given offset, + * return the text attributes for this range of text. + * + * @param includeDefAttrs [in] points whether text attributes applied to + * the entire accessible should be included or not. + * @param offset [in] text offset + * @param rangeStartOffset [out] start offset of the range of text + * @param rangeEndOffset [out] end offset of the range of text */ - nsIAccessible getAttributeRange (in long offset, - out long rangeStartOffset, - out long rangeEndOffset); + nsIPersistentProperties getTextAttributes(in boolean includeDefAttrs, + in long offset, + out long rangeStartOffset, + out long rangeEndOffset); + + /** + * Return the text attributes that apply to the entire accessible. + */ + readonly attribute nsIPersistentProperties defaultTextAttributes; /** * Returns the bounding box of the specified position. @@ -223,11 +235,6 @@ interface nsIAccessibleText : nsISupports (since not every text component will allow every operation): setSelectionBounds, addSelection, removeSelection, setCaretOffset. - getRangeAttributes defined to return an nsISupports - interface instead of a pango specific data structure. - It may be that some other return type is more appropriate - for mozilla text attributes. - we assume that all text components support the idea of a caret offset, whether visible or "virtual". If this isn't the case, caretOffset can be made readonly and diff --git a/accessible/src/atk/nsAccessibleWrap.cpp b/accessible/src/atk/nsAccessibleWrap.cpp index 2e3328987ba..e2bd1a44080 100644 --- a/accessible/src/atk/nsAccessibleWrap.cpp +++ b/accessible/src/atk/nsAccessibleWrap.cpp @@ -786,13 +786,50 @@ getRoleCB(AtkObject *aAtkObj) return aAtkObj->role; } +AtkAttributeSet* +ConvertToAtkAttributeSet(nsIPersistentProperties* aAttributes) +{ + if (!aAttributes) + return nsnull; + + AtkAttributeSet *objAttributeSet = nsnull; + nsCOMPtr propEnum; + nsresult rv = aAttributes->Enumerate(getter_AddRefs(propEnum)); + NS_ENSURE_SUCCESS(rv, nsnull); + + PRBool hasMore; + while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr sup; + rv = propEnum->GetNext(getter_AddRefs(sup)); + NS_ENSURE_SUCCESS(rv, objAttributeSet); + + nsCOMPtr propElem(do_QueryInterface(sup)); + NS_ENSURE_TRUE(propElem, objAttributeSet); + + nsCAutoString name; + rv = propElem->GetKey(name); + NS_ENSURE_SUCCESS(rv, objAttributeSet); + + nsAutoString value; + rv = propElem->GetValue(value); + NS_ENSURE_SUCCESS(rv, objAttributeSet); + + AtkAttribute *objAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute)); + objAttr->name = g_strdup(name.get()); + objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get()); + objAttributeSet = g_slist_prepend(objAttributeSet, objAttr); + } + + //libspi will free it + return objAttributeSet; +} + AtkAttributeSet * GetAttributeSet(nsIAccessible* aAccessible) { - AtkAttributeSet *objAttributeSet = nsnull; nsCOMPtr attributes; aAccessible->GetAttributes(getter_AddRefs(attributes)); - + if (attributes) { // Deal with attributes that we only need to expose in ATK PRUint32 state; @@ -804,33 +841,10 @@ GetAttributeSet(nsIAccessible* aAccessible) oldValueUnused); } - nsCOMPtr propEnum; - nsresult rv = attributes->Enumerate(getter_AddRefs(propEnum)); - NS_ENSURE_SUCCESS(rv, nsnull); - - PRBool hasMore; - while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) { - nsCOMPtr sup; - rv = propEnum->GetNext(getter_AddRefs(sup)); - nsCOMPtr propElem(do_QueryInterface(sup)); - NS_ENSURE_TRUE(propElem, nsnull); - - nsCAutoString name; - rv = propElem->GetKey(name); - NS_ENSURE_SUCCESS(rv, nsnull); - - nsAutoString value; - rv = propElem->GetValue(value); - NS_ENSURE_SUCCESS(rv, nsnull); - - AtkAttribute *objAttribute = (AtkAttribute *)g_malloc(sizeof(AtkAttribute)); - objAttribute->name = g_strdup(name.get()); - objAttribute->value = g_strdup(NS_ConvertUTF16toUTF8(value).get()); - objAttributeSet = g_slist_prepend(objAttributeSet, objAttribute); - } + return ConvertToAtkAttributeSet(attributes); } - return objAttributeSet; + return nsnull; } AtkAttributeSet * @@ -1197,6 +1211,13 @@ nsAccessibleWrap::FirePlatformEvent(nsIAccessibleEvent *aEvent) caretOffset); } break; + case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED: + MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_ATTRIBUTE_CHANGED\n")); + + g_signal_emit_by_name(atkObj, + "text-attributes-changed"); + break; + case nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_MODEL_CHANGED\n")); g_signal_emit_by_name(atkObj, "model_changed"); diff --git a/accessible/src/atk/nsMaiInterfaceText.cpp b/accessible/src/atk/nsMaiInterfaceText.cpp index a28d1340134..2f207aa17ec 100644 --- a/accessible/src/atk/nsMaiInterfaceText.cpp +++ b/accessible/src/atk/nsMaiInterfaceText.cpp @@ -41,8 +41,9 @@ #include "nsMaiInterfaceText.h" #include "nsString.h" +#include "nsIPersistentProperties2.h" -AtkAttributeSet * GetAttributeSet(nsIAccessible* aAccessible); +AtkAttributeSet* ConvertToAtkAttributeSet(nsIPersistentProperties* aAttributes); void textInterfaceInitCB(AtkTextIface *aIface) @@ -245,6 +246,9 @@ getRunAttributesCB(AtkText *aText, gint aOffset, gint *aStartOffset, gint *aEndOffset) { + *aStartOffset = -1; + *aEndOffset = -1; + nsAccessibleWrap *accWrap = GetAccessibleWrap(ATK_OBJECT(aText)); if (!accWrap) return nsnull; @@ -254,24 +258,37 @@ getRunAttributesCB(AtkText *aText, gint aOffset, getter_AddRefs(accText)); NS_ENSURE_TRUE(accText, nsnull); - nsCOMPtr accessibleWithAttrs; + nsCOMPtr attributes; PRInt32 startOffset = 0, endOffset = 0; - nsresult rv = - accText->GetAttributeRange(aOffset, &startOffset, &endOffset, - getter_AddRefs(accessibleWithAttrs)); + nsresult rv = accText->GetTextAttributes(PR_FALSE, aOffset, + &startOffset, &endOffset, + getter_AddRefs(attributes)); + NS_ENSURE_SUCCESS(rv, nsnull); + *aStartOffset = startOffset; *aEndOffset = endOffset; - if (NS_FAILED(rv)) - return nsnull; - return GetAttributeSet(accessibleWithAttrs); + return ConvertToAtkAttributeSet(attributes); } AtkAttributeSet * getDefaultAttributesCB(AtkText *aText) { - /* not supported ??? */ - return nsnull; + nsAccessibleWrap *accWrap = GetAccessibleWrap(ATK_OBJECT(aText)); + if (!accWrap) + return nsnull; + + nsCOMPtr accText; + accWrap->QueryInterface(NS_GET_IID(nsIAccessibleText), + getter_AddRefs(accText)); + NS_ENSURE_TRUE(accText, nsnull); + + nsCOMPtr attributes; + nsresult rv = accText->GetDefaultTextAttributes(getter_AddRefs(attributes)); + if (NS_FAILED(rv)) + return nsnull; + + return ConvertToAtkAttributeSet(attributes); } void diff --git a/accessible/src/base/Makefile.in b/accessible/src/base/Makefile.in index 179486fd13b..2f63a447406 100644 --- a/accessible/src/base/Makefile.in +++ b/accessible/src/base/Makefile.in @@ -90,6 +90,7 @@ CPPSRCS = \ nsApplicationAccessible.cpp \ nsCaretAccessible.cpp \ nsTextAccessible.cpp \ + nsTextUtils.cpp \ $(NULL) EXPORTS = \ diff --git a/accessible/src/base/nsAccessNode.cpp b/accessible/src/base/nsAccessNode.cpp index 0551cf46be0..6e2221eaf7e 100755 --- a/accessible/src/base/nsAccessNode.cpp +++ b/accessible/src/base/nsAccessNode.cpp @@ -876,10 +876,7 @@ nsAccessNode::GetLanguage(nsAString& aLanguage) } } - nsIContent *walkUp = content; - while (walkUp && !walkUp->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::lang, aLanguage)) { - walkUp = walkUp->GetParent(); - } + nsAccUtils::GetLanguageFor(content, nsnull, aLanguage); if (aLanguage.IsEmpty()) { // Nothing found, so use document's language nsIDocument *doc = content->GetOwnerDoc(); diff --git a/accessible/src/base/nsAccessNode.h b/accessible/src/base/nsAccessNode.h index 521e3c123c8..fd944c6f592 100755 --- a/accessible/src/base/nsAccessNode.h +++ b/accessible/src/base/nsAccessNode.h @@ -45,6 +45,8 @@ #include "nsCOMPtr.h" #include "nsAccessibilityAtoms.h" +#include "nsAccessibilityUtils.h" + #include "nsIAccessibleTypes.h" #include "nsIAccessNode.h" #include "nsIContent.h" diff --git a/accessible/src/base/nsAccessibilityAtomList.h b/accessible/src/base/nsAccessibilityAtomList.h index 4d089631295..e565c278826 100755 --- a/accessible/src/base/nsAccessibilityAtomList.h +++ b/accessible/src/base/nsAccessibilityAtomList.h @@ -149,7 +149,7 @@ ACCESSIBILITY_ATOM(tooltip, "tooltip") // XUL ACCESSIBILITY_ATOM(tr, "tr") ACCESSIBILITY_ATOM(ul, "ul") - // Alphabetical list of attributes + // Alphabetical list of attributes (DOM) ACCESSIBILITY_ATOM(acceltext, "acceltext") ACCESSIBILITY_ATOM(accesskey, "accesskey") ACCESSIBILITY_ATOM(alt, "alt") @@ -186,6 +186,10 @@ ACCESSIBILITY_ATOM(tooltiptext, "tooltiptext") ACCESSIBILITY_ATOM(type, "type") ACCESSIBILITY_ATOM(value, "value") + // Alphabetical list of text attributes (AT API) +ACCESSIBILITY_ATOM(invalid, "invalid") +ACCESSIBILITY_ATOM(language, "language") + // ARIA (DHTML accessibility) attributes // Also add to nsARIAMap.cpp and nsARIAMap.h // ARIA role attribute diff --git a/accessible/src/base/nsAccessibilityUtils.cpp b/accessible/src/base/nsAccessibilityUtils.cpp index 4a21bc3d94c..d9f111b792d 100755 --- a/accessible/src/base/nsAccessibilityUtils.cpp +++ b/accessible/src/base/nsAccessibilityUtils.cpp @@ -340,6 +340,24 @@ nsAccUtils::FireAccEvent(PRUint32 aEventType, nsIAccessible *aAccessible, return pAccessible->FireAccessibleEvent(event); } +already_AddRefed +nsAccUtils::GetDOMElementFor(nsIDOMNode *aNode) +{ + nsCOMPtr node(do_QueryInterface(aNode)); + + nsIDOMElement *element = nsnull; + if (node->IsNodeOfType(nsINode::eELEMENT)) + CallQueryInterface(node, &element); + else if (node->IsNodeOfType(nsINode::eTEXT)) + CallQueryInterface(node->GetNodeParent(), &element); + else if (node->IsNodeOfType(nsINode::eDOCUMENT)) { + nsCOMPtr domDoc(do_QueryInterface(node)); + domDoc->GetDocumentElement(&element); + } + + return element; +} + PRBool nsAccUtils::IsAncestorOf(nsIDOMNode *aPossibleAncestorNode, nsIDOMNode *aPossibleDescendantNode) @@ -921,6 +939,19 @@ nsAccUtils::FindDescendantPointingToIDImpl(nsCString& aIdWithSpaces, return nsnull; } +void +nsAccUtils::GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent, + nsAString& aLanguage) +{ + aLanguage.Truncate(); + + nsIContent *walkUp = aContent; + while (walkUp && walkUp != aRootContent && + !walkUp->GetAttr(kNameSpaceID_None, + nsAccessibilityAtoms::lang, aLanguage)) + walkUp = walkUp->GetParent(); +} + nsRoleMapEntry* nsAccUtils::GetRoleMapEntry(nsIDOMNode *aNode) { diff --git a/accessible/src/base/nsAccessibilityUtils.h b/accessible/src/base/nsAccessibilityUtils.h index 2f7d8a49121..6690e41b544 100755 --- a/accessible/src/base/nsAccessibilityUtils.h +++ b/accessible/src/base/nsAccessibilityUtils.h @@ -138,6 +138,16 @@ public: static nsresult FireAccEvent(PRUint32 aEventType, nsIAccessible *aAccessible, PRBool aIsAsynch = PR_FALSE); + /** + * Return DOM element related with the given node, i.e. + * a) itself if it is DOM element + * b) parent element if it is text node + * c) document element if it is document node. + * + * @param aNode [in] the given DOM node + */ + static already_AddRefed GetDOMElementFor(nsIDOMNode *aNode); + /** * Is the first passed in node an ancestor of the second? * Note: A node is not considered to be the ancestor of itself. @@ -302,6 +312,16 @@ public: */ static PRBool IsXLink(nsIContent *aContent); + /** + * Returns language for the given node. + * + * @param aContent [in] the given node + * @param aRootContent [in] container of the given node + * @param aLanguage [out] language + */ + static void GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent, + nsAString& aLanguage); + /** * Get the role map entry for a given DOM node. This will use the first * ARIA role if the role attribute provides a space delimited list of roles. diff --git a/accessible/src/base/nsAccessible.h b/accessible/src/base/nsAccessible.h index 882e892a676..7aed00f0e17 100644 --- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -40,7 +40,6 @@ #define _nsAccessible_H_ #include "nsAccessNodeWrap.h" -#include "nsAccessibilityUtils.h" #include "nsIAccessible.h" #include "nsPIAccessible.h" diff --git a/accessible/src/base/nsCaretAccessible.cpp b/accessible/src/base/nsCaretAccessible.cpp index 30ba3785891..7d78c9d99b2 100644 --- a/accessible/src/base/nsCaretAccessible.cpp +++ b/accessible/src/base/nsCaretAccessible.cpp @@ -47,8 +47,8 @@ #include "nsIFrame.h" #include "nsIPresShell.h" #include "nsRootAccessible.h" -#include "nsISelectionController.h" #include "nsISelectionPrivate.h" +#include "nsISelection2.h" #include "nsServiceManagerUtils.h" #include "nsIViewManager.h" #include "nsIWidget.h" @@ -78,13 +78,31 @@ void nsCaretAccessible::Shutdown() nsresult nsCaretAccessible::ClearControlSelectionListener() { - mCurrentControl = nsnull; - mCurrentControlSelection = nsnull; + nsCOMPtr controller = + GetSelectionControllerForNode(mCurrentControl); - nsCOMPtr selPrivate(do_QueryReferent(mCurrentControlSelection)); - if (!selPrivate) { + mCurrentControl = nsnull; + + if (!controller) return NS_OK; - } + + // Remove 'this' registered as selection listener for the normal selection. + nsCOMPtr normalSel; + controller->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(normalSel)); + nsCOMPtr selPrivate(do_QueryInterface(normalSel)); + NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); + + nsresult rv = selPrivate->RemoveSelectionListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + // Remove 'this' registered as selection listener for the spellcheck + // selection. + nsCOMPtr spellcheckSel; + controller->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, + getter_AddRefs(spellcheckSel)); + selPrivate = do_QueryInterface(spellcheckSel); + NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); return selPrivate->RemoveSelectionListener(this); } @@ -99,39 +117,29 @@ nsresult nsCaretAccessible::SetControlSelectionListener(nsIDOMNode *aCurrentNode mLastTextAccessible = nsnull; // When focus moves such that the caret is part of a new frame selection - // this removes the old selection listener and attaches a new one for the current focus - nsCOMPtr presShell = - mRootAccessible->GetPresShellFor(aCurrentNode); - if (!presShell) - return NS_ERROR_FAILURE; + // this removes the old selection listener and attaches a new one for + // the current focus. + nsCOMPtr controller = + GetSelectionControllerForNode(mCurrentControl); + NS_ENSURE_TRUE(controller, NS_ERROR_FAILURE); - nsCOMPtr doc = presShell->GetDocument(); - NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); - - nsCOMPtr content(do_QueryInterface(aCurrentNode)); - // The control selection listener is only for form controls, not for the document - // When there is no document, the content will be null - if (!content) { - return NS_OK; - } - - nsIFrame *frame = presShell->GetPrimaryFrameFor(content); - NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); - - nsPresContext *presContext = presShell->GetPresContext(); - NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); - - nsCOMPtr selCon; - frame->GetSelectionController(presContext, getter_AddRefs(selCon)); - NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); - - nsCOMPtr domSel; - selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(domSel)); - - nsCOMPtr selPrivate(do_QueryInterface(domSel)); + // Register 'this' as selection listener for the normal selection. + nsCOMPtr normalSel; + controller->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(normalSel)); + nsCOMPtr selPrivate(do_QueryInterface(normalSel)); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); - mCurrentControlSelection = do_GetWeakReference(domSel); + nsresult rv = selPrivate->AddSelectionListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + // Register 'this' as selection listener for the spellcheck selection. + nsCOMPtr spellcheckSel; + controller->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, + getter_AddRefs(spellcheckSel)); + selPrivate = do_QueryInterface(spellcheckSel); + NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); + return selPrivate->AddSelectionListener(this); } @@ -148,6 +156,15 @@ nsCaretAccessible::AddDocSelectionListener(nsIPresShell *aShell) nsCOMPtr selPrivate = do_QueryInterface(domSel); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); + nsresult rv = selPrivate->AddSelectionListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr spellcheckSel; + selCon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, + getter_AddRefs(spellcheckSel)); + selPrivate = do_QueryInterface(spellcheckSel); + NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); + return selPrivate->AddSelectionListener(this); } @@ -162,10 +179,39 @@ nsCaretAccessible::RemoveDocSelectionListener(nsIPresShell *aShell) nsCOMPtr selPrivate = do_QueryInterface(domSel); NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); + selPrivate->RemoveSelectionListener(this); + + nsCOMPtr spellcheckSel; + selCon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, + getter_AddRefs(spellcheckSel)); + selPrivate = do_QueryInterface(spellcheckSel); + NS_ENSURE_TRUE(selPrivate, NS_ERROR_FAILURE); + return selPrivate->RemoveSelectionListener(this); } -NS_IMETHODIMP nsCaretAccessible::NotifySelectionChanged(nsIDOMDocument *aDoc, nsISelection *aSel, PRInt16 aReason) +NS_IMETHODIMP +nsCaretAccessible::NotifySelectionChanged(nsIDOMDocument *aDoc, + nsISelection *aSel, + PRInt16 aReason) +{ + nsCOMPtr sel2(do_QueryInterface(aSel)); + + PRInt16 type = 0; + sel2->GetType(&type); + + if (type == nsISelectionController::SELECTION_NORMAL) + return NormalSelectionChanged(aDoc, aSel); + + if (type == nsISelectionController::SELECTION_SPELLCHECK) + return SpellcheckSelectionChanged(aDoc, aSel); + + return NS_OK; +} + +nsresult +nsCaretAccessible::NormalSelectionChanged(nsIDOMDocument *aDoc, + nsISelection *aSel) { NS_ENSURE_TRUE(mRootAccessible, NS_ERROR_FAILURE); @@ -247,6 +293,38 @@ NS_IMETHODIMP nsCaretAccessible::NotifySelectionChanged(nsIDOMDocument *aDoc, ns return mRootAccessible->FireDelayedAccessibleEvent(event); } +nsresult +nsCaretAccessible::SpellcheckSelectionChanged(nsIDOMDocument *aDoc, + nsISelection *aSel) +{ + // XXX: fire an event for accessible of focus node of the selection. If + // spellchecking is enabled then we will fire the number of events for + // the same accessible for newly appended range of the selection (for every + // misspelled word). If spellchecking is disabled (for example, + // @spellcheck="false" on html:body) then we won't fire any event. + nsCOMPtr targetNode; + aSel->GetFocusNode(getter_AddRefs(targetNode)); + if (!targetNode) + return NS_OK; + + nsCOMPtr docAccessible = + nsAccessNode::GetDocAccessibleFor(targetNode); + NS_ENSURE_STATE(docAccessible); + + nsCOMPtr containerAccessible; + nsresult rv = + docAccessible->GetAccessibleInParentChain(targetNode, PR_TRUE, + getter_AddRefs(containerAccessible)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr event = + new nsAccEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, + containerAccessible, nsnull); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + + return mRootAccessible->FireAccessibleEvent(event); +} + nsRect nsCaretAccessible::GetCaretRect(nsIWidget **aOutWidget) { @@ -317,3 +395,35 @@ nsCaretAccessible::GetCaretRect(nsIWidget **aOutWidget) return caretRect; } +already_AddRefed +nsCaretAccessible::GetSelectionControllerForNode(nsIDOMNode *aNode) +{ + if (!aNode) + return nsnull; + + nsCOMPtr presShell = mRootAccessible->GetPresShellFor(aNode); + if (!presShell) + return nsnull; + + nsCOMPtr doc = presShell->GetDocument(); + if (!doc) + return nsnull; + + // Get selection controller only for form controls, not for the document. + nsCOMPtr content(do_QueryInterface(aNode)); + if (!content) + return nsnull; + + nsIFrame *frame = presShell->GetPrimaryFrameFor(content); + if (!frame) + return nsnull; + + nsPresContext *presContext = presShell->GetPresContext(); + if (!presContext) + return nsnull; + + nsISelectionController *controller = nsnull; + frame->GetSelectionController(presContext, &controller); + return controller; +} + diff --git a/accessible/src/base/nsCaretAccessible.h b/accessible/src/base/nsCaretAccessible.h index c75910406f4..9fcc96343b5 100644 --- a/accessible/src/base/nsCaretAccessible.h +++ b/accessible/src/base/nsCaretAccessible.h @@ -42,6 +42,7 @@ #include "nsIAccessibleText.h" #include "nsIDOMNode.h" #include "nsISelectionListener.h" +#include "nsISelectionController.h" #include "nsRect.h" class nsRootAccessible; @@ -120,17 +121,23 @@ public: nsRect GetCaretRect(nsIWidget **aOutWidget); +protected: + nsresult NormalSelectionChanged(nsIDOMDocument *aDoc, nsISelection *aSel); + nsresult SpellcheckSelectionChanged(nsIDOMDocument *aDoc, nsISelection *aSel); + + already_AddRefed + GetSelectionControllerForNode(nsIDOMNode *aNode); + private: // The currently focused control -- never a document. // We listen to selection for one control at a time (the focused one) // Document selection is handled separately via additional listeners on all active documents // The current control is set via SetControlSelectionListener() nsCOMPtr mCurrentControl; // Selection controller for the currently focused control - nsCOMPtr mCurrentControlSelection; - // Info for the the last selection event - // If it was on a control, then mLastUsedSelection == mCurrentControlSelection - // Otherwise, it's for a document where the selection changed + // Info for the the last selection event. + // If it was on a control, then it's control's selection. Otherwise, it's for + // a document where the selection changed. nsCOMPtr mLastUsedSelection; // Weak ref to nsISelection nsCOMPtr mLastTextAccessible; PRInt32 mLastCaretOffset; diff --git a/accessible/src/html/nsHyperTextAccessible.cpp b/accessible/src/html/nsHyperTextAccessible.cpp index 2f4766b01e8..04626d2735e 100644 --- a/accessible/src/html/nsHyperTextAccessible.cpp +++ b/accessible/src/html/nsHyperTextAccessible.cpp @@ -41,6 +41,8 @@ #include "nsAccessibilityAtoms.h" #include "nsAccessibilityService.h" #include "nsAccessibleTreeWalker.h" +#include "nsTextUtils.h" + #include "nsPIAccessNode.h" #include "nsIClipboard.h" #include "nsContentCID.h" @@ -50,6 +52,7 @@ #include "nsPIDOMWindow.h" #include "nsIDOMDocumentView.h" #include "nsIDOMRange.h" +#include "nsIDOMNSRange.h" #include "nsIDOMWindowInternal.h" #include "nsIDOMXULDocument.h" #include "nsIEditingSession.h" @@ -671,7 +674,7 @@ nsresult nsHyperTextAccessible::DOMPointToHypertextOffset(nsIDOMNode* aNode, PRI // Start offset, inclusive // Make sure the offset lands on the embedded object character in order to indicate // the true inner offset is inside the subtree for that link - addTextOffset = (TextLength(descendantAccessible) == addTextOffset) ? 1 : 0; + addTextOffset = (TextLength(descendantAccessible) == static_cast(addTextOffset)) ? 1 : 0; } descendantAccessible = parentAccessible; } @@ -698,6 +701,18 @@ nsresult nsHyperTextAccessible::DOMPointToHypertextOffset(nsIDOMNode* aNode, PRI return NS_OK; } +nsresult +nsHyperTextAccessible::HypertextOffsetToDOMPoint(PRInt32 aHTOffset, + nsIDOMNode **aNode, + PRInt32 *aOffset) +{ + nsCOMPtr endNode; + PRInt32 endOffset; + + return HypertextOffsetsToDOMRange(aHTOffset, aHTOffset, aNode, aOffset, + getter_AddRefs(endNode), &endOffset); +} + nsresult nsHyperTextAccessible::HypertextOffsetsToDOMRange(PRInt32 aStartHTOffset, PRInt32 aEndHTOffset, @@ -888,11 +903,15 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe // otherwise screen readers will announce the wrong line as the user presses up or down arrow and land // at the end of a line. nsCOMPtr domSel; - nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel)); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, getter_AddRefs(domSel)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr privateSelection(do_QueryInterface(domSel)); nsCOMPtr frameSelection; rv = privateSelection->GetFrameSelection(getter_AddRefs(frameSelection)); NS_ENSURE_SUCCESS(rv, rv); + if (frameSelection->GetHint() == nsFrameSelection::HINTLEFT) { -- aOffset; // We are at the start of a line } @@ -1071,31 +1090,103 @@ NS_IMETHODIMP nsHyperTextAccessible::GetTextAfterOffset(PRInt32 aOffset, nsAcces return GetTextHelper(eGetAfter, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText); } -NS_IMETHODIMP nsHyperTextAccessible::GetAttributeRange(PRInt32 aOffset, PRInt32 *aRangeStartOffset, - PRInt32 *aRangeEndOffset, nsIAccessible **aAccessibleWithAttrs) +// nsIPersistentProperties +// nsIAccessibleText::getTextAttributes(in boolean includeDefAttrs, +// in long offset, +// out long rangeStartOffset, +// out long rangeEndOffset); +NS_IMETHODIMP +nsHyperTextAccessible::GetTextAttributes(PRBool aIncludeDefAttrs, + PRInt32 aOffset, + PRInt32 *aStartOffset, + PRInt32 *aEndOffset, + nsIPersistentProperties **aAttributes) { - // Return the range of text with common attributes around aOffset - *aRangeStartOffset = *aRangeEndOffset = 0; - *aAccessibleWithAttrs = nsnull; + // 1. First we get spell check, then language, then the set of CSS-based + // attributes. + // 2. As we get each new attribute, we pass the current start and end offsets + // as in/out parameters. In other words, as attributes are collected, + // the attribute range itself can only stay the same or get smaller. + // + // Example: + // Current: range 5-10 + // Adding: range 7-12 + // Result: range 7-10 - if (!mDOMNode) { + NS_ENSURE_ARG_POINTER(aStartOffset); + *aStartOffset = 0; + + NS_ENSURE_ARG_POINTER(aEndOffset); + nsresult rv = GetCharacterCount(aEndOffset); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_ARG_POINTER(aAttributes); + *aAttributes = nsnull; + + nsCOMPtr attributes = + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); + NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY); + + NS_ADDREF(*aAttributes = attributes); + + if (!mDOMNode) return NS_ERROR_FAILURE; - } - nsCOMPtr accessible; - - while (NextChild(accessible)) { - PRInt32 length = TextLength(accessible); - NS_ENSURE_TRUE(length >= 0, NS_ERROR_FAILURE); - if (*aRangeStartOffset + length > aOffset) { - *aRangeEndOffset = *aRangeStartOffset + length; - NS_ADDREF(*aAccessibleWithAttrs = accessible); - return NS_OK; - } - *aRangeStartOffset += length; - } + nsCOMPtr node; + PRInt32 nodeOffset = 0; + rv = HypertextOffsetToDOMPoint(aOffset, getter_AddRefs(node), &nodeOffset); + NS_ENSURE_SUCCESS(rv, rv); - return NS_ERROR_FAILURE; + // Set 'misspelled' text attribute. + rv = GetSpellTextAttribute(node, nodeOffset, aStartOffset, aEndOffset, + *aAttributes); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr content(do_QueryInterface(node)); + if (content && content->IsNodeOfType(nsINode::eELEMENT)) + node = do_QueryInterface(content->GetChildAt(nodeOffset)); + + if (!node) + return NS_OK; + + // Set 'lang' text attribute. + rv = GetLangTextAttributes(aIncludeDefAttrs, node, + aStartOffset, aEndOffset, *aAttributes); + NS_ENSURE_SUCCESS(rv, rv); + + // Set CSS based text attributes. + rv = GetCSSTextAttributes(aIncludeDefAttrs, node, + aStartOffset, aEndOffset, *aAttributes); + return rv; +} + +// nsIPersistentProperties +// nsIAccessibleText::defaultTextAttributes +NS_IMETHODIMP +nsHyperTextAccessible::GetDefaultTextAttributes(nsIPersistentProperties **aAttributes) +{ + NS_ENSURE_ARG_POINTER(aAttributes); + *aAttributes = nsnull; + + nsCOMPtr attributes = + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); + NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY); + + NS_ADDREF(*aAttributes = attributes); + + if (!mDOMNode) + return NS_ERROR_FAILURE; + + nsCOMPtr element = nsAccUtils::GetDOMElementFor(mDOMNode); + + nsCSSTextAttr textAttr(PR_TRUE, element, nsnull); + while (textAttr.iterate()) { + nsCAutoString name; + nsAutoString value, oldValue; + if (textAttr.get(name, value)) + attributes->SetStringProperty(name, value, oldValue); + } + return NS_OK; } nsresult @@ -1474,7 +1565,8 @@ nsresult nsHyperTextAccessible::SetSelectionRange(PRInt32 aStartPos, PRInt32 aEn // ranges remaining from previous selection nsCOMPtr domSel; nsCOMPtr selCon; - GetSelections(getter_AddRefs(selCon), getter_AddRefs(domSel)); + GetSelections(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selCon), getter_AddRefs(domSel)); if (domSel) { PRInt32 numRanges; domSel->GetRangeCount(&numRanges); @@ -1509,7 +1601,8 @@ NS_IMETHODIMP nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset) *aCaretOffset = 0; nsCOMPtr domSel; - nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel)); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, getter_AddRefs(domSel)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr caretNode; @@ -1527,7 +1620,8 @@ PRInt32 nsHyperTextAccessible::GetCaretLineNumber() // Provide the line number for the caret, relative to the // currently focused node. Use a 1-based index nsCOMPtr domSel; - GetSelections(nsnull, getter_AddRefs(domSel)); + GetSelections(nsISelectionController::SELECTION_NORMAL, nsnull, + getter_AddRefs(domSel)); nsCOMPtr privateSelection(do_QueryInterface(domSel)); NS_ENSURE_TRUE(privateSelection, -1); nsCOMPtr frameSelection; @@ -1590,9 +1684,11 @@ PRInt32 nsHyperTextAccessible::GetCaretLineNumber() return lineNumber; } -nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon, - nsISelection **aDomSel, - nsCOMArray* aRanges) +nsresult +nsHyperTextAccessible::GetSelections(PRInt16 aType, + nsISelectionController **aSelCon, + nsISelection **aDomSel, + nsCOMArray* aRanges) { if (!mDOMNode) { return NS_ERROR_FAILURE; @@ -1610,7 +1706,6 @@ nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon, nsCOMPtr domSel; nsCOMPtr selCon; - nsCOMPtr startNode; nsCOMPtr editor; GetAssociatedEditor(getter_AddRefs(editor)); nsCOMPtr peditor(do_QueryInterface(editor)); @@ -1619,18 +1714,7 @@ nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon, // This is for form controls which have their own // selection controller separate from the document, for example // HTML:input, HTML:textarea, XUL:textbox, etc. - if (aSelCon) { - editor->GetSelectionController(getter_AddRefs(selCon)); - NS_ENSURE_TRUE(*aSelCon, NS_ERROR_FAILURE); - } - - editor->GetSelection(getter_AddRefs(domSel)); - NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE); - - nsCOMPtr editorRoot; - editor->GetRootElement(getter_AddRefs(editorRoot)); - startNode = do_QueryInterface(editorRoot); - NS_ENSURE_STATE(startNode); + editor->GetSelectionController(getter_AddRefs(selCon)); } else { // Case 2: rich content subtree (can be rich editor) @@ -1641,13 +1725,11 @@ nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon, // Get the selection and selection controller frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon)); - NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); - selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, - getter_AddRefs(domSel)); - NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE); - - startNode = mDOMNode; } + NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); + + selCon->GetSelection(aType, getter_AddRefs(domSel)); + NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE); if (aSelCon) { NS_ADDREF(*aSelCon = selCon); @@ -1655,10 +1737,19 @@ nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon, if (aDomSel) { NS_ADDREF(*aDomSel = domSel); } + if (aRanges) { nsCOMPtr selection2(do_QueryInterface(domSel)); NS_ENSURE_TRUE(selection2, NS_ERROR_FAILURE); + nsCOMPtr startNode(mDOMNode); + if (peditor) { + nsCOMPtr editorRoot; + editor->GetRootElement(getter_AddRefs(editorRoot)); + startNode = do_QueryInterface(editorRoot); + } + NS_ENSURE_STATE(startNode); + nsCOMPtr childNodes; nsresult rv = startNode->GetChildNodes(getter_AddRefs(childNodes)); NS_ENSURE_SUCCESS(rv, rv); @@ -1692,7 +1783,8 @@ NS_IMETHODIMP nsHyperTextAccessible::GetSelectionCount(PRInt32 *aSelectionCount) { nsCOMPtr domSel; nsCOMArray ranges; - nsresult rv = GetSelections(nsnull, nsnull, &ranges); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, nsnull, &ranges); NS_ENSURE_SUCCESS(rv, rv); *aSelectionCount = ranges.Count(); @@ -1709,7 +1801,8 @@ NS_IMETHODIMP nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, P nsCOMPtr domSel; nsCOMArray ranges; - nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel), &ranges); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, getter_AddRefs(domSel), &ranges); NS_ENSURE_SUCCESS(rv, rv); PRInt32 rangeCount = ranges.Count(); @@ -1762,7 +1855,8 @@ nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum, PRInt32 aEndOffset) { nsCOMPtr domSel; - nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel)); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, getter_AddRefs(domSel)); NS_ENSURE_SUCCESS(rv, rv); // Caret is a collapsed selection @@ -1810,7 +1904,8 @@ nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum, NS_IMETHODIMP nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 aEndOffset) { nsCOMPtr domSel; - nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel)); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, getter_AddRefs(domSel)); NS_ENSURE_SUCCESS(rv, rv); PRInt32 rangeCount; @@ -1825,7 +1920,8 @@ NS_IMETHODIMP nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 NS_IMETHODIMP nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum) { nsCOMPtr domSel; - nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel)); + nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL, + nsnull, getter_AddRefs(domSel)); NS_ENSURE_SUCCESS(rv, rv); PRInt32 rangeCount; @@ -2037,3 +2133,352 @@ nsHyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame *aFrame, return NS_OK; } +// nsHyperTextAccessible +nsresult +nsHyperTextAccessible::DOMRangeBoundToHypertextOffset(nsIDOMRange *aRange, + PRBool aIsStartBound, + PRBool aIsStartHTOffset, + PRInt32 *aHTOffset) +{ + nsCOMPtr node; + PRInt32 nodeOffset = 0; + + nsresult rv; + if (aIsStartBound) { + rv = aRange->GetStartContainer(getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aRange->GetStartOffset(&nodeOffset); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = aRange->GetEndContainer(getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aRange->GetEndOffset(&nodeOffset); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr startAcc; + rv = DOMPointToHypertextOffset(node, nodeOffset, aHTOffset, + getter_AddRefs(startAcc)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aIsStartHTOffset && !startAcc) + *aHTOffset = 0; + + return NS_OK; +} + +// nsHyperTextAccessible +nsresult +nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode, + PRInt32 aNodeOffset, + PRInt32 *aHTStartOffset, + PRInt32 *aHTEndOffset, + nsIPersistentProperties *aAttributes) +{ + nsCOMArray ranges; + nsresult rv = GetSelections(nsISelectionController::SELECTION_SPELLCHECK, + nsnull, nsnull, &ranges); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt32 rangeCount = ranges.Count(); + if (!rangeCount) + return NS_OK; + + for (PRInt32 index = 0; index < rangeCount; index++) { + nsCOMPtr range = ranges[index]; + nsCOMPtr nsrange(do_QueryInterface(range)); + NS_ENSURE_STATE(nsrange); + + PRInt16 result; + rv = nsrange->ComparePoint(aNode, aNodeOffset, &result); + NS_ENSURE_SUCCESS(rv, rv); + + if (result == 1) { // range is before point + PRInt32 startHTOffset = 0; + rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_TRUE, + &startHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + + if (startHTOffset > *aHTStartOffset) + *aHTStartOffset = startHTOffset; + + } else if (result == -1) { // range is after point + PRInt32 endHTOffset = 0; + rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_FALSE, + &endHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + + if (endHTOffset < *aHTEndOffset) + *aHTEndOffset = endHTOffset; + + } else { // point is in range + PRInt32 startHTOffset = 0; + rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_TRUE, + &startHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt32 endHTOffset = 0; + rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_FALSE, + &endHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + + if (startHTOffset > *aHTStartOffset) + *aHTStartOffset = startHTOffset; + if (endHTOffset < *aHTEndOffset) + *aHTEndOffset = endHTOffset; + + nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::invalid, + NS_LITERAL_STRING("spelling")); + + return NS_OK; + } + } + + return NS_OK; +} + +// nsHyperTextAccessible +nsresult +nsHyperTextAccessible::GetLangTextAttributes(PRBool aIncludeDefAttrs, + nsIDOMNode *aSourceNode, + PRInt32 *aStartHTOffset, + PRInt32 *aEndHTOffset, + nsIPersistentProperties *aAttributes) +{ + nsCOMPtr sourceElm(nsAccUtils::GetDOMElementFor(aSourceNode)); + + nsCOMPtr content(do_QueryInterface(sourceElm)); + nsCOMPtr rootContent(do_QueryInterface(mDOMNode)); + + nsAutoString lang; + nsAccUtils::GetLanguageFor(content, rootContent, lang); + + nsAutoString rootLang; + nsresult rv = GetLanguage(rootLang); + NS_ENSURE_SUCCESS(rv, rv); + + // Expose 'language' text attribute if the DOM 'lang' attribute is + // presented and it's different from the 'lang' attribute on the root + // element or we should include default values of text attribute. + const nsAString& resultLang = lang.IsEmpty() ? rootLang : lang; + if (!resultLang.IsEmpty() && (aIncludeDefAttrs || lang != rootLang)) + nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::language, + resultLang); + + nsLangTextAttr textAttr(lang, rootContent); + return GetRangeForTextAttr(aSourceNode, &textAttr, + aStartHTOffset, aEndHTOffset); +} + +// nsHyperTextAccessible +nsresult +nsHyperTextAccessible::GetCSSTextAttributes(PRBool aIncludeDefAttrs, + nsIDOMNode *aSourceNode, + PRInt32 *aStartHTOffset, + PRInt32 *aEndHTOffset, + nsIPersistentProperties *aAttributes) +{ + nsCOMPtr sourceElm(nsAccUtils::GetDOMElementFor(aSourceNode)); + nsCOMPtr rootElm(nsAccUtils::GetDOMElementFor(mDOMNode)); + + nsCSSTextAttr textAttr(aIncludeDefAttrs, sourceElm, rootElm); + while (textAttr.iterate()) { + nsCAutoString name; + nsAutoString value, oldValue; + if (textAttr.get(name, value)) + aAttributes->SetStringProperty(name, value, oldValue); + + nsresult rv = GetRangeForTextAttr(aSourceNode, &textAttr, + aStartHTOffset, aEndHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// nsHyperTextAccessible +nsresult +nsHyperTextAccessible::GetRangeForTextAttr(nsIDOMNode *aNode, + nsTextAttr *aComparer, + PRInt32 *aStartHTOffset, + PRInt32 *aEndHTOffset) +{ + nsCOMPtr rootElm(nsAccUtils::GetDOMElementFor(mDOMNode)); + NS_ENSURE_STATE(rootElm); + + nsCOMPtr tmpNode(aNode); + nsCOMPtr currNode(aNode); + + // Navigate backwards and forwards from current node to the root node to + // calculate range bounds for the text attribute. Navigation sequence is the + // following: + // 1. Navigate through the siblings. + // 2. If the traversed sibling has children then navigate from its leaf child + // to it through whole tree of the traversed sibling. + // 3. Get the parent and cycle algorithm until the root node. + + // Navigate backwards (find the start offset). + while (currNode && currNode != rootElm) { + nsCOMPtr currElm(nsAccUtils::GetDOMElementFor(currNode)); + NS_ENSURE_STATE(currElm); + + if (currNode != aNode && !aComparer->equal(currElm)) { + PRInt32 startHTOffset = 0; + nsCOMPtr startAcc; + nsresult rv = DOMPointToHypertextOffset(tmpNode, -1, &startHTOffset, + getter_AddRefs(startAcc)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!startAcc) + startHTOffset = 0; + + if (startHTOffset > *aStartHTOffset) + *aStartHTOffset = startHTOffset; + + break; + } + + currNode->GetPreviousSibling(getter_AddRefs(tmpNode)); + if (tmpNode) { + // Navigate through the subtree of traversed children to calculate + // left bound of the range. + FindStartOffsetInSubtree(tmpNode, currNode, aComparer, aStartHTOffset); + } + + currNode->GetParentNode(getter_AddRefs(tmpNode)); + currNode.swap(tmpNode); + } + + // Navigate forwards (find the end offset). + PRBool moveIntoSubtree = PR_TRUE; + currNode = aNode; + while (currNode && currNode != rootElm) { + nsCOMPtr currElm(nsAccUtils::GetDOMElementFor(currNode)); + NS_ENSURE_STATE(currElm); + + // Stop new end offset searching if the given text attribute changes its + // value. + if (!aComparer->equal(currElm)) { + PRInt32 endHTOffset = 0; + nsresult rv = DOMPointToHypertextOffset(currNode, -1, &endHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + + if (endHTOffset < *aEndHTOffset) + *aEndHTOffset = endHTOffset; + + break; + } + + if (moveIntoSubtree) { + // Navigate through subtree of traversed node. We use 'moveIntoSubtree' + // flag to avoid traversing the same subtree twice. + currNode->GetFirstChild(getter_AddRefs(tmpNode)); + if (tmpNode) + FindEndOffsetInSubtree(tmpNode, aComparer, aEndHTOffset); + } + + currNode->GetNextSibling(getter_AddRefs(tmpNode)); + moveIntoSubtree = PR_TRUE; + if (!tmpNode) { + currNode->GetParentNode(getter_AddRefs(tmpNode)); + moveIntoSubtree = PR_FALSE; + } + + currNode.swap(tmpNode); + } + + return NS_OK; +} + + +PRBool +nsHyperTextAccessible::FindEndOffsetInSubtree(nsIDOMNode *aCurrNode, + nsTextAttr *aComparer, + PRInt32 *aHTOffset) +{ + if (!aCurrNode) + return PR_FALSE; + + nsCOMPtr currElm(nsAccUtils::GetDOMElementFor(aCurrNode)); + NS_ENSURE_STATE(currElm); + + // If the given text attribute (pointed by nsTextAttr object) changes its + // value on the traversed element then fit the end of range. + if (!aComparer->equal(currElm)) { + PRInt32 endHTOffset = 0; + nsresult rv = DOMPointToHypertextOffset(aCurrNode, -1, &endHTOffset); + NS_ENSURE_SUCCESS(rv, rv); + + if (endHTOffset < *aHTOffset) + *aHTOffset = endHTOffset; + + return PR_TRUE; + } + + // Deeply traverse into the tree to fit the end of range. + nsCOMPtr nextNode; + aCurrNode->GetFirstChild(getter_AddRefs(nextNode)); + if (nextNode) { + PRBool res = FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset); + if (res) + return res; + } + + aCurrNode->GetNextSibling(getter_AddRefs(nextNode)); + if (nextNode) { + if (FindEndOffsetInSubtree(nextNode, aComparer, aHTOffset)) + return PR_TRUE; + } + + return PR_FALSE; +} + +PRBool +nsHyperTextAccessible::FindStartOffsetInSubtree(nsIDOMNode *aCurrNode, + nsIDOMNode *aPrevNode, + nsTextAttr *aComparer, + PRInt32 *aHTOffset) +{ + if (!aCurrNode) + return PR_FALSE; + + // Find the closest element back to the traversed element. + nsCOMPtr nextNode; + aCurrNode->GetLastChild(getter_AddRefs(nextNode)); + if (nextNode) { + if (FindStartOffsetInSubtree(nextNode, aPrevNode, aComparer, aHTOffset)) + return PR_TRUE; + } + + nsCOMPtr currElm(nsAccUtils::GetDOMElementFor(aCurrNode)); + NS_ENSURE_STATE(currElm); + + // If the given text attribute (pointed by nsTextAttr object) changes its + // value on the traversed element then fit the start of range. + if (!aComparer->equal(currElm)) { + PRInt32 startHTOffset = 0; + nsCOMPtr startAcc; + nsresult rv = DOMPointToHypertextOffset(aPrevNode, -1, &startHTOffset, + getter_AddRefs(startAcc)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!startAcc) + startHTOffset = 0; + + if (startHTOffset > *aHTOffset) + *aHTOffset = startHTOffset; + + return PR_TRUE; + } + + // Moving backwards to find the start of range. + aCurrNode->GetPreviousSibling(getter_AddRefs(nextNode)); + if (nextNode) { + if (FindStartOffsetInSubtree(nextNode, aCurrNode, aComparer, aHTOffset)) + return PR_TRUE; + } + + return PR_FALSE; +} diff --git a/accessible/src/html/nsHyperTextAccessible.h b/accessible/src/html/nsHyperTextAccessible.h index a65057e6573..26eb7ed4916 100644 --- a/accessible/src/html/nsHyperTextAccessible.h +++ b/accessible/src/html/nsHyperTextAccessible.h @@ -45,6 +45,8 @@ #include "nsIAccessibleHyperText.h" #include "nsIAccessibleEditableText.h" #include "nsAccessibleEventData.h" +#include "nsTextUtils.h" + #include "nsFrameSelection.h" #include "nsISelectionController.h" @@ -64,7 +66,6 @@ const PRUnichar kForcedNewLineChar = '\n'; { 0xa9, 0x2e, 0x95, 0x23, 0x97, 0x05, 0xf3, 0x0b } \ } - /** * Special Accessible that knows how contain both text and embedded objects */ @@ -81,8 +82,11 @@ public: NS_DECL_NSIACCESSIBLEEDITABLETEXT NS_DECLARE_STATIC_IID_ACCESSOR(NS_HYPERTEXTACCESSIBLE_IMPL_CID) + // nsIAccessible NS_IMETHOD GetRole(PRUint32 *aRole); NS_IMETHOD GetState(PRUint32 *aState, PRUint32 *aExtraState); + + // nsAccessible virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes); void CacheChildren(); @@ -124,6 +128,17 @@ public: nsIAccessible **aFinalAccessible = nsnull, PRBool aIsEndOffset = PR_FALSE); + /** + * Turn a hypertext offsets into DOM point. + * + * @param aHTOffset [in] the given start hypertext offset + * @param aNode [out] start node + * @param aOffset [out] offset inside the start node + */ + nsresult HypertextOffsetToDOMPoint(PRInt32 aHTOffset, + nsIDOMNode **aNode, + PRInt32 *aOffset); + /** * Turn a start and end hypertext offsets into DOM range. * @@ -211,15 +226,23 @@ protected: // Selection helpers - /** - * Get the relevant selection interfaces and ranges for the current hyper text - * @param aSelCon The selection controller for the current hyper text, or nsnull if not needed - * @param aDomSel The selection interface for the current hyper text, or nsnull if not needed - * @param aRanges The selected ranges within the current subtree, or nsnull if not needed + /** + * Get the relevant selection interfaces and ranges for the current hyper + * text. + * + * @param aType [in] the selection type + * @param aSelCon [out, optional] the selection controller for the current + * hyper text + * @param aDomSel [out, optional] the selection interface for the current + * hyper text + * @param aRanges [out, optional] the selected ranges within the current + * subtree */ - nsresult GetSelections(nsISelectionController **aSelCon, + nsresult GetSelections(PRInt16 aType, + nsISelectionController **aSelCon, nsISelection **aDomSel = nsnull, nsCOMArray* aRanges = nsnull); + nsresult SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos); /** @@ -233,6 +256,125 @@ protected: nsresult GetDOMPointByFrameOffset(nsIFrame *aFrame, PRInt32 aOffset, nsIAccessible *aAccessible, nsIDOMNode **aNode, PRInt32 *aNodeOffset); + + + /** + * Return hyper text offset for the specified bound of the given DOM range. + * If the bound is outside of the hyper text then offset value is either + * 0 or number of characters of hyper text, it depends on type of requested + * offset. The method is a wrapper for DOMPointToHypertextOffset. + * + * @param aRange [in] the given range + * @param aIsStartBound [in] specifies whether the required range bound is + * start bound + * @param aIsStartOffset [in] the offset type, used when the range bound is + * outside of hyper text + * @param aHTOffset [out] the result offset + */ + nsresult DOMRangeBoundToHypertextOffset(nsIDOMRange *aRange, + PRBool aIsStartBound, + PRBool aIsStartOffset, + PRInt32 *aHTOffset); + + /** + * Set 'misspelled' text attribute and return range offsets where the + * attibute is stretched. If the text is not misspelled at the given offset + * then we expose only range offsets where text is not misspelled. The method + * is used by GetTextAttributes() method. + * + * @param aIncludeDefAttrs [in] points whether text attributes having default + * values of attributes should be included + * @param aSourceNode [in] the node we start to traverse from + * @param aStartOffset [in, out] the start offset + * @param aEndOffset [in, out] the end offset + * @param aAttributes [out] result attributes + */ + nsresult GetSpellTextAttribute(nsIDOMNode *aNode, PRInt32 aNodeOffset, + PRInt32 *aStartOffset, + PRInt32 *aEndOffset, + nsIPersistentProperties *aAttributes); + + /** + * Set 'lang' text attribute and return range offsets where attibute is + * stretched. The method is used by GetTextAttributes() method. + * + * @param aIncludeDefAttrs [in] points whether text attributes having default + * values of attributes should be included + * @param aSourceNode [in] the node we start to traverse from + * @param aStartOffset [in, out] the start offset + * @param aEndOffset [in, out] the end offset + * @param aAttributes [out] result attributes + */ + nsresult GetLangTextAttributes(PRBool aIncludeDefAttrs, + nsIDOMNode *aSourceNode, + PRInt32 *aStartOffset, + PRInt32 *aEndOffset, + nsIPersistentProperties *aAttributes); + + /** + * Set CSS based text attribute and return range offsets where attibutes are + * stretched. The method is used by GetTextAttributes() method. + * + * @param aIncludeDefAttrs [in] points whether text attributes having default + * values of attributes should be included + * @param aSourceNode [in] the node we start to traverse from + * @param aStartOffset [in, out] the start offset + * @param aEndOffset [in, out] the end offset + * @param aAttributes [out] result attributes + */ + nsresult GetCSSTextAttributes(PRBool aIncludeDefAttrs, + nsIDOMNode *aSourceNode, + PRInt32 *aStartOffset, + PRInt32 *aEndOffset, + nsIPersistentProperties *aAttributes); + + /** + * Calculates range (start and end offsets) of text where the text attribute + * (pointed by nsTextAttr object) is stretched. New offsets may be smaller if + * the given text attribute changes its value before or after the given + * offsets. + * + * @param aNode [in] the node we start to traverse from + * @param aComparer [in] object used to describe the text attribute + * @param aStartHTOffset [in, out] the start offset + * @param aEndHTOffset [in, out] the end offset + */ + nsresult GetRangeForTextAttr(nsIDOMNode *aNode, + nsTextAttr *aComparer, + PRInt32 *aStartHTOffset, + PRInt32 *aEndHTOffset); + + /** + * Find new end offset for text attributes navigating through the tree. New + * end offset may be smaller if the given text attribute (pointed by + * nsTextAttr object) changes its value before the given end offset. + * + * @param aCurrNode [in] the first node of the tree + * @param aComparer [in] object used to describe the text attribute + * @param aHTOffset [in, out] the end offset + * @return true if the end offset has been changed + */ + PRBool FindEndOffsetInSubtree(nsIDOMNode *aCurrNode, + nsTextAttr *aComparer, + PRInt32 *aHTOffset); + + /** + * Find the start offset for text attributes navigating through the tree. New + * start offset may be bigger if the given text attribute (pointed by + * nsTextAttr object) changes its value after the given start offset. + * + * @param aCurrNode [in] the node navigating through thee thee is started + * from + * @param aPrevNode [in] the previous node placed before the start node + * @param aComparer [in] object used to describe the text attribute + * @param aHTOffset [in, out] the start offset + * @return true if the start offset has been changed + */ + PRBool FindStartOffsetInSubtree(nsIDOMNode *aCurrNode, + nsIDOMNode *aPrevNode, + nsTextAttr *aComparer, + PRInt32 *aHTOffset); + }; NS_DEFINE_STATIC_IID_ACCESSOR(nsHyperTextAccessible, diff --git a/accessible/src/msaa/CAccessibleText.cpp b/accessible/src/msaa/CAccessibleText.cpp index 0bee36f4779..4f6852fa1cd 100755 --- a/accessible/src/msaa/CAccessibleText.cpp +++ b/accessible/src/msaa/CAccessibleText.cpp @@ -48,6 +48,7 @@ #include "nsIAccessibleTypes.h" #include "nsIWinAccessNode.h" #include "nsAccessNodeWrap.h" +#include "nsAccessibleWrap.h" #include "nsCOMPtr.h" #include "nsString.h" @@ -108,32 +109,26 @@ __try { GET_NSIACCESSIBLETEXT - nsCOMPtr accessible; PRInt32 startOffset = 0, endOffset = 0; - textAcc->GetAttributeRange(aOffset, &startOffset, &endOffset, - getter_AddRefs(accessible)); - if (!accessible) - return E_FAIL; - - nsCOMPtr winAccessNode(do_QueryInterface(accessible)); - if (!winAccessNode) - return E_FAIL; - - void *instancePtr = 0; - winAccessNode->QueryNativeInterface(IID_IAccessible2, &instancePtr); - if (!instancePtr) - return E_FAIL; - - IAccessible2 *pAccessible2 = static_cast(instancePtr); - HRESULT hr = pAccessible2->get_attributes(aTextAttributes); - pAccessible2->Release(); + nsCOMPtr attributes; + nsresult rv = textAcc->GetTextAttributes(PR_TRUE, aOffset, + &startOffset, &endOffset, + getter_AddRefs(attributes)); + if (NS_FAILED(rv)) + return GetHRESULT(rv); + + HRESULT hr = nsAccessibleWrap::ConvertToIA2Attributes(attributes, + aTextAttributes); + if (FAILED(hr)) + return hr; *aStartOffset = startOffset; *aEndOffset = endOffset; - return hr; + + return S_OK; } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } - return E_NOTIMPL; + return E_FAIL; } STDMETHODIMP diff --git a/accessible/src/msaa/nsAccessibleWrap.cpp b/accessible/src/msaa/nsAccessibleWrap.cpp index e437a974eaf..0b6552786a5 100644 --- a/accessible/src/msaa/nsAccessibleWrap.cpp +++ b/accessible/src/msaa/nsAccessibleWrap.cpp @@ -1584,60 +1584,7 @@ __try { if (NS_FAILED(rv)) return GetHRESULT(rv); - if (!attributes) - return S_FALSE; - - nsCOMPtr propEnum; - attributes->Enumerate(getter_AddRefs(propEnum)); - if (!propEnum) - return E_FAIL; - - nsAutoString strAttrs; - - const char kCharsToEscape[] = ":;=,\\"; - - PRBool hasMore = PR_FALSE; - while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) { - nsCOMPtr propSupports; - propEnum->GetNext(getter_AddRefs(propSupports)); - - nsCOMPtr propElem(do_QueryInterface(propSupports)); - if (!propElem) - return E_FAIL; - - nsCAutoString name; - rv = propElem->GetKey(name); - if (NS_FAILED(rv)) - return GetHRESULT(rv); - - PRUint32 offset = 0; - while ((offset = name.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { - name.Insert('\\', offset); - offset += 2; - } - - nsAutoString value; - rv = propElem->GetValue(value); - if (NS_FAILED(rv)) - return E_FAIL; - - offset = 0; - while ((offset = value.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { - value.Insert('\\', offset); - offset += 2; - } - - AppendUTF8toUTF16(name, strAttrs); - strAttrs.Append(':'); - strAttrs.Append(value); - strAttrs.Append(';'); - } - - if (strAttrs.IsEmpty()) - return S_FALSE; - - *aAttributes = ::SysAllocStringLen(strAttrs.get(), strAttrs.Length()); - return *aAttributes ? S_OK : E_OUTOFMEMORY; + return ConvertToIA2Attributes(attributes, aAttributes); } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } return E_FAIL; @@ -1829,6 +1776,69 @@ nsAccessibleWrap::GetHWNDFor(nsIAccessible *aAccessible) return hWnd; } +HRESULT +nsAccessibleWrap::ConvertToIA2Attributes(nsIPersistentProperties *aAttributes, + BSTR *aIA2Attributes) +{ + *aIA2Attributes = NULL; + + // The format is name:value;name:value; with \ for escaping these + // characters ":;=,\". + + if (!aAttributes) + return S_FALSE; + + nsCOMPtr propEnum; + aAttributes->Enumerate(getter_AddRefs(propEnum)); + if (!propEnum) + return E_FAIL; + + nsAutoString strAttrs; + + const char kCharsToEscape[] = ":;=,\\"; + + PRBool hasMore = PR_FALSE; + while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr propSupports; + propEnum->GetNext(getter_AddRefs(propSupports)); + + nsCOMPtr propElem(do_QueryInterface(propSupports)); + if (!propElem) + return E_FAIL; + + nsCAutoString name; + if (NS_FAILED(propElem->GetKey(name))) + return E_FAIL; + + PRUint32 offset = 0; + while ((offset = name.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { + name.Insert('\\', offset); + offset += 2; + } + + nsAutoString value; + if (NS_FAILED(propElem->GetValue(value))) + return E_FAIL; + + offset = 0; + while ((offset = value.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { + value.Insert('\\', offset); + offset += 2; + } + + AppendUTF8toUTF16(name, strAttrs); + strAttrs.Append(':'); + strAttrs.Append(value); + strAttrs.Append(';'); + } + + if (strAttrs.IsEmpty()) + return S_FALSE; + + *aIA2Attributes = ::SysAllocStringLen(strAttrs.get(), strAttrs.Length()); + return *aIA2Attributes ? S_OK : E_OUTOFMEMORY; +} + IDispatch *nsAccessibleWrap::NativeAccessible(nsIAccessible *aXPAccessible) { if (!aXPAccessible) { diff --git a/accessible/src/msaa/nsAccessibleWrap.h b/accessible/src/msaa/nsAccessibleWrap.h index 8a5107ece80..f587434afbb 100644 --- a/accessible/src/msaa/nsAccessibleWrap.h +++ b/accessible/src/msaa/nsAccessibleWrap.h @@ -296,6 +296,8 @@ class nsAccessibleWrap : public nsAccessible, // Helper methods static PRInt32 GetChildIDFor(nsIAccessible* aAccessible); static HWND GetHWNDFor(nsIAccessible *aAccessible); + static HRESULT ConvertToIA2Attributes(nsIPersistentProperties *aAttributes, + BSTR *aIA2Attributes); /** * System caret support: update the Windows caret position. diff --git a/accessible/tests/mochitest/Makefile.in b/accessible/tests/mochitest/Makefile.in index 584819b866f..5ec459bc8aa 100644 --- a/accessible/tests/mochitest/Makefile.in +++ b/accessible/tests/mochitest/Makefile.in @@ -69,6 +69,7 @@ _TEST_FILES =\ test_nsIAccessibleHyperText.html \ test_nsIAccessibleImage.html \ test_nsOuterDocAccessible.html \ + test_textattrs.html \ test_textboxes.html \ test_textboxes.xul \ testTextboxes.js \ diff --git a/content/base/public/nsISelection2.idl b/content/base/public/nsISelection2.idl index 390c6ea172f..c894c5cdc04 100644 --- a/content/base/public/nsISelection2.idl +++ b/content/base/public/nsISelection2.idl @@ -21,6 +21,7 @@ * * Contributor(s): * Brett Wilson + * Alexander Surkov * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -47,9 +48,18 @@ interface nsIDOMRange; [ptr] native RangeArray(nsCOMArray); -[scriptable, uuid(b515878d-3b06-433b-bc9e-5c53d2fa3eff)] +[scriptable, uuid(5d21d5fe-3691-4716-a334-4691eea54d29)] interface nsISelection2 : nsISelection { + /** + * Returns the type of the selection (see nsISelectionController for + * available constants). + */ + readonly attribute short type; + + /** + * Return array of ranges intersecting with the given DOM interval. + */ void GetRangesForInterval( in nsIDOMNode beginNode, in PRInt32 beginOffset, in nsIDOMNode endNode, in PRInt32 endOffset, diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index b67ecf1d3c4..6889f111afe 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -4696,6 +4696,15 @@ nsTypedSelection::MoveIndexToNextMismatch(PRInt32* aIndex, nsIDOMNode* aNode, return NS_OK; } +NS_IMETHODIMP +nsTypedSelection::GetType(PRInt16 *aType) +{ + NS_ENSURE_ARG_POINTER(aType); + *aType = mType; + + return NS_OK; +} + // nsTypedSelection::GetRangesForInterval // // XPCOM wrapper for the COMArray version