Bug 448744 - IAccessibleText::caretOffset should return -1 if the system caret is not currently with in that particular object, r=aaronlev, marcoz, sr=roc

This commit is contained in:
Alexander Surkov 2008-12-16 18:14:20 +08:00
Родитель 9c3c12487c
Коммит 73b5a29c6d
8 изменённых файлов: 274 добавлений и 122 удалений

Просмотреть файл

@ -448,29 +448,16 @@ nsAccUtils::GetTextAccessibleFromSelection(nsISelection *aSelection,
// Get accessible from selection's focus DOM point (the DOM point where
// selection is ended).
nsCOMPtr<nsIDOMNode> resultNode;
aSelection->GetFocusNode(getter_AddRefs(resultNode));
if (!resultNode)
nsCOMPtr<nsIDOMNode> focusNode;
aSelection->GetFocusNode(getter_AddRefs(focusNode));
if (!focusNode)
return nsnull;
// Get DOM node that focus DOM point points to.
nsCOMPtr<nsIContent> content(do_QueryInterface(resultNode));
if (content && content->IsNodeOfType(nsINode::eELEMENT)) {
PRInt32 offset = 0;
aSelection->GetFocusOffset(&offset);
PRInt32 focusOffset = 0;
aSelection->GetFocusOffset(&focusOffset);
PRInt32 childCount = static_cast<PRInt32>(content->GetChildCount());
NS_ASSERTION(offset >= 0 && offset <= childCount,
"Wrong focus offset in selection!");
// The offset can be after last child of container node that means DOM point
// is placed immediately after the last child. In this case use focusNode
// as result node.
if (offset != childCount) {
nsCOMPtr<nsIContent> child = content->GetChildAt(offset);
resultNode = do_QueryInterface(child);
}
}
nsCOMPtr<nsIDOMNode> resultNode =
nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
nsIAccessibilityService *accService = nsAccessNode::GetAccService();

Просмотреть файл

@ -192,6 +192,31 @@ nsCoreUtils::GetDOMElementFor(nsIDOMNode *aNode)
return element;
}
already_AddRefed<nsIDOMNode>
nsCoreUtils::GetDOMNodeFromDOMPoint(nsIDOMNode *aNode, PRUint32 aOffset)
{
nsIDOMNode *resultNode = nsnull;
nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
if (content && content->IsNodeOfType(nsINode::eELEMENT)) {
PRInt32 childCount = static_cast<PRInt32>(content->GetChildCount());
NS_ASSERTION(aOffset >= 0 && aOffset <= childCount,
"Wrong offset of the DOM point!");
// The offset can be after last child of container node that means DOM point
// is placed immediately after the last child. In this case use the DOM node
// from the given DOM point is used as result node.
if (aOffset != childCount) {
CallQueryInterface(content->GetChildAt(aOffset), &resultNode);
return resultNode;
}
}
NS_IF_ADDREF(resultNode = aNode);
return resultNode;
}
nsIContent*
nsCoreUtils::GetRoleContent(nsIDOMNode *aDOMNode)
{

Просмотреть файл

@ -86,6 +86,11 @@ public:
*/
static already_AddRefed<nsIDOMElement> GetDOMElementFor(nsIDOMNode *aNode);
/**
* Return DOM node for the given DOM point.
*/
static already_AddRefed<nsIDOMNode> GetDOMNodeFromDOMPoint(nsIDOMNode *aNode,
PRUint32 aOffset);
/**
* Return the nsIContent* to check for ARIA attributes on -- this may not
* always be the DOM node for the accessible. Specifically, for doc

Просмотреть файл

@ -1632,23 +1632,46 @@ NS_IMETHODIMP nsHyperTextAccessible::SetCaretOffset(PRInt32 aCaretOffset)
/*
* Gets the offset position of the caret (cursor).
*/
NS_IMETHODIMP nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
NS_IMETHODIMP
nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
{
*aCaretOffset = 0;
*aCaretOffset = -1;
// No caret if the focused node is not inside this DOM node and this DOM node
// is not inside of focused node.
PRBool isInsideOfFocusedNode =
nsCoreUtils::IsAncestorOf(gLastFocusedNode, mDOMNode);
if (!isInsideOfFocusedNode && mDOMNode != gLastFocusedNode &&
!nsCoreUtils::IsAncestorOf(mDOMNode, gLastFocusedNode))
return NS_OK;
// Turn the focus node and offset of the selection into caret hypretext
// offset.
nsCOMPtr<nsISelection> domSel;
nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
nsnull, getter_AddRefs(domSel));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMNode> caretNode;
rv = domSel->GetFocusNode(getter_AddRefs(caretNode));
nsCOMPtr<nsIDOMNode> focusNode;
rv = domSel->GetFocusNode(getter_AddRefs(focusNode));
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 caretOffset;
domSel->GetFocusOffset(&caretOffset);
PRInt32 focusOffset;
rv = domSel->GetFocusOffset(&focusOffset);
NS_ENSURE_SUCCESS(rv, rv);
return DOMPointToHypertextOffset(caretNode, caretOffset, aCaretOffset);
// 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 (isInsideOfFocusedNode) {
nsCOMPtr<nsIDOMNode> resultNode =
nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
if (resultNode != mDOMNode &&
!nsCoreUtils::IsAncestorOf(mDOMNode, resultNode))
return NS_OK;
}
return DOMPointToHypertextOffset(focusNode, focusOffset, aCaretOffset);
}
PRInt32 nsHyperTextAccessible::GetCaretLineNumber()

Просмотреть файл

@ -145,7 +145,7 @@ __try {
return GetHRESULT(rv);
*aOffset = offset;
return S_OK;
return offset != -1 ? S_OK : S_FALSE;
} __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
return E_FAIL;

Просмотреть файл

@ -6,6 +6,8 @@ const nsIAccessibleRetrieval = Components.interfaces.nsIAccessibleRetrieval;
const nsIAccessibleEvent = Components.interfaces.nsIAccessibleEvent;
const nsIAccessibleStateChangeEvent =
Components.interfaces.nsIAccessibleStateChangeEvent;
const nsIAccessibleCaretMoveEvent =
Components.interfaces.nsIAccessibleCaretMoveEvent;
const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates;
const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole;
@ -73,7 +75,29 @@ const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE;
var gAccRetrieval = null;
/**
* Return accessible for the given ID attribute or DOM element.
* Return the DOM node.
*/
function getNode(aNodeOrID)
{
if (!aNodeOrID)
return null;
var node = aNodeOrID;
if (!(aNodeOrID instanceof nsIDOMNode)) {
node = document.getElementById(aNodeOrID);
if (!node) {
ok(false, "Can't get DOM element for " + aNodeOrID);
return null;
}
}
return node;
}
/**
* Return accessible for the given ID attribute or DOM element or accessible.
*
* @param aAccOrElmOrID [in] DOM element or ID attribute to get an accessible
* for or an accessible to query additional interfaces.
@ -210,6 +234,7 @@ function unregisterA11yEventListener(aEventType, aEventHandler)
* invoke: function(){}, // generates event for the DOM node
* check: function(aEvent){}, // checks event for correctness
* DOMNode getter() {} // DOM node event is generated for
* getID: function(){} // returns invoker ID
* };
*
* @param aEventType the given event type
@ -235,8 +260,11 @@ function eventQueue(aEventType)
if (aQueue.mIndex == aQueue.mInvokers.length - 1) {
unregisterA11yEventListener(aQueue.mEventType, aQueue.mEventHandler);
for (var idx = 0; idx < aQueue.mInvokers.length; idx++)
ok(aQueue.mInvokers[idx].wasCaught, "test " + idx + " failed.");
for (var idx = 0; idx < aQueue.mInvokers.length; idx++) {
var invoker = aQueue.mInvokers[idx];
ok(invoker.wasCaught,
"test with ID = '" + invoker.getID() + "' failed.");
}
SimpleTest.finish();
return;
@ -246,7 +274,7 @@ function eventQueue(aEventType)
aQueue.invoke();
},
100, this
200, this
);
}
@ -309,6 +337,12 @@ function eventHandlerForEventQueue(aQueue)
this.handleEvent = function eventHandlerForEventQueue_handleEvent(aEvent)
{
var invoker = this.mQueue.getInvoker();
if (!invoker) // skip events before test was started
return;
if ("debugCheck" in invoker)
invoker.debugCheck(aEvent);
if (aEvent.DOMNode == invoker.DOMNode) {
invoker.check(aEvent);
invoker.wasCaught = true;

Просмотреть файл

@ -17,114 +17,183 @@
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript">
function synthMouseTest(aNode)
/**
* Invoker base class.
*/
function synthAction(aNodeOrID, aCaretOffset)
{
this.node = aNode;
this.testFunc = function testFunc()
this.DOMNode = getNode(aNodeOrID);
this.check = function synthAction_check(aEvent)
{
synthesizeMouse(this.node, 1, 1, {});
is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset,
this.caretOffset,
"Wrong caret offset for " + aNodeOrID);
}
this.getID = function synthAction_getID() { return aNodeOrID + " action"; }
this.caretOffset = aCaretOffset;
}
/**
* Click invoker.
*/
function synthClick(aNodeOrID, aCaretOffset,
aExtraNodeOrID, aExtraCaretOffset)
{
this.__proto__ = new synthAction(aNodeOrID, aCaretOffset);
this.extraNode = getNode(aExtraNodeOrID);
this.extraCaretOffset = aExtraCaretOffset;
this.invoke = function synthClick_invoke()
{
// Scroll the node into view, otherwise synth click may fail.
this.DOMNode.scrollIntoView(true);
synthesizeMouse(this.DOMNode, 1, 1, {});
}
this.check = function synthFocus_check(aEvent)
{
this.__proto__.check(aEvent);
if (this.extraNode) {
var acc = getAccessible(this.extraNode, [nsIAccessibleText]);
is(acc.caretOffset, this.extraCaretOffset,
"Wrong caret offset for " + aExtraNodeOrID);
}
}
this.getID = function synthFocus_getID() { return aNodeOrID + " click"; }
}
/**
* Key press invokers.
*/
function synthKey(aNodeOrID, aCaretOffset, aKey, aArgs)
{
this.__proto__ = new synthAction(aNodeOrID, aCaretOffset);
this.invoke = function synthKey_invoke()
{
synthesizeKey(this.mKey, this.mArgs);
}
this.mKey = aKey;
this.mArgs = aArgs ? aArgs : {};
}
function synthTabTest(aNodeOrID, aCaretOffset, aBackTab)
{
this.__proto__ = new synthKey(aNodeOrID, aCaretOffset,
"VK_TAB", {shiftKey: aBackTab});
this.getID = function synthTabTest_getID() { return aNodeOrID + " tab"; }
}
function synthDownKey(aNodeOrID, aCaretOffset)
{
this.__proto__ = new synthKey(aNodeOrID, aCaretOffset, "VK_DOWN");
this.getID = function synthDownKey_getID()
{
return aNodeOrID + " key down";
}
}
function synthKeyTest(aNode, aKey)
function synthRightKey(aNodeOrID, aCaretOffset)
{
this.node = aNode;
this.testFunc = function testFunc()
this.__proto__ = new synthKey(aNodeOrID, aCaretOffset, "VK_RIGHT");
this.getID = function synthRightKey_getID()
{
synthesizeKey(aKey, {});
return aNodeOrID + " key right";
}
}
function synthTabTest(aNode, aBackTab)
/**
* Focus invoker.
*/
function synthFocus(aNodeOrID, aCaretOffset)
{
this.node = aNode;
this.testFunc = function testFunc()
this.__proto__ = new synthAction(aNodeOrID, aCaretOffset);
this.invoke = function synthFocus_invoke()
{
synthesizeKey("VK_TAB", {shiftKey: aBackTab});
this.DOMNode.focus();
}
this.getID = function synthFocus_getID() { return aNodeOrID + " focus"; }
}
/**
* Select all invoker.
*/
function synthSelectAll(aNodeOrID, aCaretOffset)
{
this.__proto__ = new synthAction(aNodeOrID, aCaretOffset);
this.invoke = function synthSelectAll_invoke()
{
if (this.DOMNode instanceof Components.interfaces.nsIDOMHTMLInputElement)
this.DOMNode.select();
else
window.getSelection().selectAllChildren(this.DOMNode);
}
this.getID = function synthSelectAll_getID()
{
return aNodeOrID + " selectall";
}
}
function synthFocusTest(aNode)
/**
* Do tests.
*/
var gQueue = null;
function testCaretOffset(aAccOrElmOrID, aCaretOffset)
{
// bug 460417
this.node = aNode;
this.testFunc = function testFunc()
{
this.node.focus();
}
}
function synthSelectAllTest(aNode)
{
// bug 460417
this.node = aNode;
this.testFunc = function testFunc()
{
this.node.select();
}
}
var gTestsArray = [];
var gTestIdx = -1;
var gCaretMoveHandler =
{
handleEvent: function handleEvent(aEvent)
{
if (aEvent.DOMNode == gTestsArray[gTestIdx].node)
gTestsArray[gTestIdx].wasCaught = true;
}
};
function doTest()
{
window.setTimeout(
function()
{
if (gTestIdx == gTestsArray.length - 1) {
unregisterA11yEventListener(nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED,
gCaretMoveHandler);
for (var idx = 0; idx < gTestsArray.length; idx++)
ok(gTestsArray[idx].wasCaught, "test " + idx + " failed");
SimpleTest.finish();
return;
}
gTestsArray[++gTestIdx].testFunc();
doTest();
},
100
);
var acc = getAccessible(aAccOrElmOrID, [nsIAccessibleText]);
is(acc.caretOffset, aCaretOffset,
"Wrong caret offset for " + aAccOrElmOrID);
}
function doTests()
{
var textbox = document.getElementById("textbox");
gTestsArray.push(new synthFocusTest(textbox));
gTestsArray.push(new synthSelectAllTest(textbox));
gTestsArray.push(new synthMouseTest(textbox));
gTestsArray.push(new synthKeyTest(textbox, "VK_RIGHT"));
// test no focused accessibles
testCaretOffset("textbox", -1);
testCaretOffset("textarea", -1);
testCaretOffset("p", -1);
var textarea = document.getElementById("textarea");
gTestsArray.push(new synthMouseTest(textarea));
gTestsArray.push(new synthKeyTest(textarea, "VK_RIGHT"));
gTestsArray.push(new synthKeyTest(textarea, "VK_DOWN"));
// test caret move events and caret offsets
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED);
var p = document.getElementById("p");
gTestsArray.push(new synthMouseTest(p));
gTestsArray.push(new synthKeyTest(p, "VK_RIGHT"));
gTestsArray.push(new synthKeyTest(p, "VK_DOWN"));
var id = "textbox";
gQueue.push(new synthFocus(id, 5));
gQueue.push(new synthSelectAll(id, 5));
gQueue.push(new synthClick(id, 0));
gQueue.push(new synthRightKey(id, 1));
gTestsArray.push(new synthTabTest(textarea, true));
gTestsArray.push(new synthTabTest(p));
id = "textarea";
gQueue.push(new synthClick(id, 0));
gQueue.push(new synthRightKey(id, 1));
gQueue.push(new synthDownKey(id, 12));
registerA11yEventListener(nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED,
gCaretMoveHandler);
id = "p";
gQueue.push(new synthClick(id, 0));
gQueue.push(new synthRightKey(id, 1));
gQueue.push(new synthDownKey(id, 6));
doTest();
gQueue.push(new synthClick("p1_in_div", 0, "p2_in_div", -1));
gQueue.push(new synthTabTest("p", 0, true));
gQueue.push(new synthTabTest("textarea", 12, true));
gQueue.push(new synthTabTest("p", 0, false));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
@ -147,6 +216,7 @@
<input id="textbox" value="hello"/>
<textarea id="textarea">text<br>text</textarea>
<p id="p" contentEditable="true"><span>text</span><br/>text</p>
<div id="div" contentEditable="true"><p id="p1_in_div">text</p><p id="p2_in_div">text</p></div>
</body>
</html>

Просмотреть файл

@ -20,7 +20,7 @@
<script type="application/javascript">
<![CDATA[
function openHideCombobox(aComboboxNode, aIsOpen)
function openHideCombobox(aComboboxNodeOrID, aIsOpen)
{
this.invoke = function invoke()
{
@ -30,13 +30,21 @@
{
aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
var id = this.getID();
is(aEvent.state, nsIAccessibleStates.STATE_EXPANDED,
"Wrong state change event is handled.");
"Wrong state change event is handled in test '" + id + "'.");
is(aEvent.isEnabled(), this.mIsOpen,
"Wrong value of state expanded.");
"Wrong value of state expanded in test '" + id + "'.");
}
this.getID = function getID()
{
if (this.mIsOpen)
return this.DOMNodeOrID + " open combobox";
return this.DOMNodeOrID + " close combobox";
}
this.DOMNode = aComboboxNode;
this.DOMNodeOrID = aComboboxNodeOrID;
this.DOMNode = getNode(aComboboxNodeOrID);
this.mIsOpen = aIsOpen;
}
@ -45,9 +53,9 @@
{
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE);
var menulist = document.getElementById("menulist");
gQueue.push(new openHideCombobox(menulist, true));
gQueue.push(new openHideCombobox(menulist, false));
var ID = "menulist";
gQueue.push(new openHideCombobox(ID, true));
gQueue.push(new openHideCombobox(ID, false));
// XXX: searchbar doesn't fire state change events because accessible
// parent of combobox_list accessible is pushbutton accessible.