Bug 673958 - rework accessible focus handling, r=enndeaking, marcoz, tbsaunde, matspal, f=marcoz

--HG--
rename : accessible/tests/mochitest/test_aria_activedescendant.html => accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
rename : accessible/tests/mochitest/events/test_focus.html => accessible/tests/mochitest/events/test_focus_dialog.html
rename : accessible/tests/mochitest/events/test_focusdoc.html => accessible/tests/mochitest/events/test_focus_doc.html
rename : accessible/tests/mochitest/events/test_focus.xul => accessible/tests/mochitest/events/test_focus_general.xul
rename : accessible/tests/mochitest/states/test_comboboxes.xul => accessible/tests/mochitest/states/test_expandable.xul
rename : accessible/tests/mochitest/test_nsIAccessible_selects.html => accessible/tests/mochitest/states/test_selects.html
This commit is contained in:
Alexander Surkov 2011-09-28 10:46:11 +09:00
parent 616ecf38f9
commit 6a04618807
92 changed files with 4701 additions and 1628 deletions

View File

@ -1241,7 +1241,7 @@ nsAccessibleWrap::FirePlatformEvent(AccEvent* aEvent)
g_signal_emit(atkObj, id, 0);
// Always fire a current focus event after activation.
rootAcc->FireCurrentFocusEvent();
FocusMgr()->ForceFocusEvent();
} break;
case nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE:

View File

@ -78,9 +78,9 @@ public:
// will be emitted.
eCoalesceFromSameSubtree,
// eCoalesceFromSameDocument : For events of the same type from the same
// document, only the newest event will be emitted.
eCoalesceFromSameDocument,
// eCoalesceOfSameType : For events of the same type, only the newest event
// will be processed.
eCoalesceOfSameType,
// eRemoveDupes : For repeat events, only the newest event in queue
// will be emitted.

View File

@ -0,0 +1,361 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alexander Surkov <surkov.alexander@gmail.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "FocusManager.h"
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsRootAccessible.h"
#include "nsFocusManager.h"
namespace dom = mozilla::dom;
using namespace mozilla::a11y;
FocusManager::FocusManager()
{
}
FocusManager::~FocusManager()
{
}
nsAccessible*
FocusManager::FocusedAccessible() const
{
if (mActiveItem)
return mActiveItem;
nsINode* focusedNode = FocusedDOMNode();
if (focusedNode)
return GetAccService()->GetAccessibleOrContainer(focusedNode, nsnull);
return nsnull;
}
bool
FocusManager::IsFocused(const nsAccessible* aAccessible) const
{
if (mActiveItem)
return mActiveItem == aAccessible;
nsINode* focusedNode = FocusedDOMNode();
if (focusedNode) {
// XXX: Before getting an accessible for node having a DOM focus make sure
// they belong to the same document because it can trigger unwanted document
// accessible creation for temporary about:blank document. Without this
// peculiarity we would end up with plain implementation based on
// FocusedAccessible() method call. Make sure this issue is fixed in
// bug 638465.
if (focusedNode->GetOwnerDoc() == aAccessible->GetNode()->GetOwnerDoc()) {
return aAccessible ==
GetAccService()->GetAccessibleOrContainer(focusedNode, nsnull);
}
}
return false;
}
bool
FocusManager::IsFocusWithin(const nsAccessible* aContainer) const
{
nsAccessible* child = FocusedAccessible();
while (child) {
if (child == aContainer)
return true;
child = child->Parent();
}
return false;
}
FocusManager::FocusDisposition
FocusManager::IsInOrContainsFocus(const nsAccessible* aAccessible) const
{
nsAccessible* focus = FocusedAccessible();
if (!focus)
return eNone;
// If focused.
if (focus == aAccessible)
return eFocused;
// If contains the focus.
nsAccessible* child = focus->Parent();
while (child) {
if (child == aAccessible)
return eContainsFocus;
child = child->Parent();
}
// If contained by focus.
child = aAccessible->Parent();
while (child) {
if (child == focus)
return eContainedByFocus;
child = child->Parent();
}
return eNone;
}
void
FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
{
A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET("DOM focus", "DOM focus target",
aTarget)
mActiveItem = nsnull;
nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
if (targetNode) {
nsDocAccessible* document =
GetAccService()->GetDocAccessible(targetNode->GetOwnerDoc());
if (document) {
// Set selection listener for focused element.
if (targetNode->IsElement()) {
nsRootAccessible* root = document->RootAccessible();
nsCaretAccessible* caretAcc = root->GetCaretAccessible();
caretAcc->SetControlSelectionListener(targetNode->AsElement());
}
document->HandleNotification<FocusManager, nsINode>
(this, &FocusManager::ProcessDOMFocus, targetNode);
}
}
}
void
FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
{
A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET("DOM blur", "DOM blur target",
aTarget)
mActiveItem = nsnull;
// If DOM document stays focused then fire accessible focus event to process
// the case when no element within this DOM document will be focused.
nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
if (targetNode && targetNode->GetOwnerDoc() == FocusedDOMDocument()) {
nsIDocument* DOMDoc = targetNode->GetOwnerDoc();
nsDocAccessible* document =
GetAccService()->GetDocAccessible(DOMDoc);
if (document) {
document->HandleNotification<FocusManager, nsINode>
(this, &FocusManager::ProcessDOMFocus, DOMDoc);
}
}
}
void
FocusManager::ActiveItemChanged(nsAccessible* aItem, bool aCheckIfActive)
{
A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET("active item changed",
"Active item", aItem)
// Nothing changed, happens for XUL trees and HTML selects.
if (aItem && aItem == mActiveItem)
return;
mActiveItem = nsnull;
if (aItem && aCheckIfActive) {
nsAccessible* widget = aItem->ContainerWidget();
A11YDEBUG_FOCUS_LOG_WIDGET("Active item widget", widget)
if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
return;
}
mActiveItem = aItem;
// If active item is changed then fire accessible focus event on it, otherwise
// if there's no an active item then fire focus event to accessible having
// DOM focus.
nsAccessible* target = FocusedAccessible();
if (target)
DispatchFocusEvent(target->GetDocAccessible(), target);
}
void
FocusManager::ForceFocusEvent()
{
nsINode* focusedNode = FocusedDOMNode();
if (focusedNode) {
nsDocAccessible* document =
GetAccService()->GetDocAccessible(focusedNode->GetOwnerDoc());
if (document) {
document->HandleNotification<FocusManager, nsINode>
(this, &FocusManager::ProcessDOMFocus, focusedNode);
}
}
}
void
FocusManager::DispatchFocusEvent(nsDocAccessible* aDocument,
nsAccessible* aTarget)
{
NS_PRECONDITION(aDocument, "No document for focused accessible!");
if (aDocument) {
nsRefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
eAutoDetect, AccEvent::eCoalesceOfSameType);
aDocument->FireDelayedAccessibleEvent(event);
A11YDEBUG_FOCUS_LOG_ACCTARGET("Focus notification", aTarget)
}
}
void
FocusManager::ProcessDOMFocus(nsINode* aTarget)
{
A11YDEBUG_FOCUS_NOTIFICATION_DOMTARGET("Process DOM focus",
"Notification target", aTarget)
nsDocAccessible* document =
GetAccService()->GetDocAccessible(aTarget->GetOwnerDoc());
nsAccessible* target = document->GetAccessibleOrContainer(aTarget);
if (target) {
// Check if still focused. Otherwise we can end up with storing the active
// item for control that isn't focused anymore.
nsAccessible* DOMFocus =
GetAccService()->GetAccessibleOrContainer(FocusedDOMNode(), nsnull);
if (target != DOMFocus)
return;
nsAccessible* activeItem = target->CurrentItem();
if (activeItem) {
mActiveItem = activeItem;
target = activeItem;
}
DispatchFocusEvent(document, target);
}
}
void
FocusManager::ProcessFocusEvent(AccEvent* aEvent)
{
NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
"Focus event is expected!");
EIsFromUserInput fromUserInputFlag = aEvent->IsFromUserInput() ?
eFromUserInput : eNoUserInput;
// Emit focus event if event target is the active item. Otherwise then check
// if it's still focused and then update active item and emit focus event.
nsAccessible* target = aEvent->GetAccessible();
if (target != mActiveItem) {
// Check if still focused. Otherwise we can end up with storing the active
// item for control that isn't focused anymore.
nsAccessible* DOMFocus =
GetAccService()->GetAccessibleOrContainer(FocusedDOMNode(), nsnull);
if (target != DOMFocus)
return;
nsAccessible* activeItem = target->CurrentItem();
if (activeItem) {
mActiveItem = activeItem;
target = activeItem;
}
}
// Fire menu start/end events for ARIA menus.
if (target->ARIARole() == nsIAccessibleRole::ROLE_MENUITEM) {
// The focus was moved into menu.
nsAccessible* ARIAMenubar =
nsAccUtils::GetAncestorWithRole(target, nsIAccessibleRole::ROLE_MENUBAR);
if (ARIAMenubar != mActiveARIAMenubar) {
// Leaving ARIA menu. Fire menu_end event on current menubar.
if (mActiveARIAMenubar) {
nsRefPtr<AccEvent> menuEndEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
fromUserInputFlag);
nsEventShell::FireEvent(menuEndEvent);
}
mActiveARIAMenubar = ARIAMenubar;
// Entering ARIA menu. Fire menu_start event.
if (mActiveARIAMenubar) {
nsRefPtr<AccEvent> menuStartEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
mActiveARIAMenubar, fromUserInputFlag);
nsEventShell::FireEvent(menuStartEvent);
}
}
} else if (mActiveARIAMenubar) {
// Focus left a menu. Fire menu_end event.
nsRefPtr<AccEvent> menuEndEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
fromUserInputFlag);
nsEventShell::FireEvent(menuEndEvent);
mActiveARIAMenubar = nsnull;
}
A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET("FIRE FOCUS EVENT", "Focus target",
target)
nsRefPtr<AccEvent> focusEvent =
new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, fromUserInputFlag);
nsEventShell::FireEvent(focusEvent);
}
nsIContent*
FocusManager::FocusedDOMElm() const
{
nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
return DOMFocusManager->GetFocusedContent();
}
nsIDocument*
FocusManager::FocusedDOMDocument() const
{
nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
nsCOMPtr<nsIDOMWindow> focusedWnd;
DOMFocusManager->GetFocusedWindow(getter_AddRefs(focusedWnd));
if (focusedWnd) {
nsCOMPtr<nsIDOMDocument> DOMDoc;
focusedWnd->GetDocument(getter_AddRefs(DOMDoc));
nsCOMPtr<nsIDocument> DOMDocNode(do_QueryInterface(DOMDoc));
return DOMDocNode;
}
return nsnull;
}

View File

@ -0,0 +1,297 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alexander Surkov <surkov.alexander@gmail.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef mozilla_a11y_FocusManager_h_
#define mozilla_a11y_FocusManager_h_
#include "nsAutoPtr.h"
#include "mozilla/dom/Element.h"
class AccEvent;
class nsAccessible;
class nsDocAccessible;
namespace mozilla {
namespace a11y {
/**
* Manage the accessible focus. Used to fire and process accessible events.
*/
class FocusManager
{
public:
virtual ~FocusManager();
/**
* Return a focused accessible.
*/
nsAccessible* FocusedAccessible() const;
/**
* Return true if given accessible is focused.
*/
bool IsFocused(const nsAccessible* aAccessible) const;
/**
* Return true if the given accessible is an active item, i.e. an item that
* is current within the active widget.
*/
inline bool IsActiveItem(const nsAccessible* aAccessible)
{ return aAccessible == mActiveItem; }
/**
* Return true if given DOM node has DOM focus.
*/
inline bool HasDOMFocus(const nsINode* aNode) const
{ return aNode == FocusedDOMNode(); }
/**
* Return true if focused accessible is within the given container.
*/
bool IsFocusWithin(const nsAccessible* aContainer) const;
/**
* Return whether the given accessible is focused or contains the focus or
* contained by focused accessible.
*/
enum FocusDisposition {
eNone,
eFocused,
eContainsFocus,
eContainedByFocus
};
FocusDisposition IsInOrContainsFocus(const nsAccessible* aAccessible) const;
//////////////////////////////////////////////////////////////////////////////
// Focus notifications and processing (don't use until you know what you do).
/**
* Called when DOM focus event is fired.
*/
void NotifyOfDOMFocus(nsISupports* aTarget);
/**
* Called when DOM blur event is fired.
*/
void NotifyOfDOMBlur(nsISupports* aTarget);
/**
* Called when active item is changed. Note: must be called when accessible
* tree is up to date.
*/
void ActiveItemChanged(nsAccessible* aItem, bool aCheckIfActive = true);
/**
* Dispatch delayed focus event for the current focus accessible.
*/
void ForceFocusEvent();
/**
* Dispatch delayed focus event for the given target.
*/
void DispatchFocusEvent(nsDocAccessible* aDocument, nsAccessible* aTarget);
/**
* Process DOM focus notification.
*/
void ProcessDOMFocus(nsINode* aTarget);
/**
* Process the delayed accessible event.
* do.
*/
void ProcessFocusEvent(AccEvent* aEvent);
protected:
FocusManager();
private:
FocusManager(const FocusManager&);
FocusManager& operator =(const FocusManager&);
/**
* Return DOM node having DOM focus.
*/
inline nsINode* FocusedDOMNode() const
{
nsINode* focusedNode = FocusedDOMElm();
if (focusedNode)
return focusedNode;
return FocusedDOMDocument();
}
/**
* Return DOM element having DOM focus.
*/
nsIContent* FocusedDOMElm() const;
/**
* Return DOM document having DOM focus.
*/
nsIDocument* FocusedDOMDocument() const;
private:
nsRefPtr<nsAccessible> mActiveItem;
nsRefPtr<nsAccessible> mActiveARIAMenubar;
};
} // namespace a11y
} // namespace mozilla
//#define A11YDEBUG_FOCUS
#ifdef A11YDEBUG_FOCUS
// Util macros (don't use them directly)
#define A11YDEBUG_FOCUS_STARTBLOCK \
printf(" {\n ");
#define A11YDEBUG_FOCUS_ENDBLOCK \
printf("\n }\n");
#define A11YDEBUG_FOCUS_BLOCKOFFSET \
printf(" ");
#define A11YDEBUG_FOCUS_LOG_TIME \
{ \
PRIntervalTime time = PR_IntervalNow(); \
PRUint32 mins = (PR_IntervalToSeconds(time) / 60) % 60; \
PRUint32 secs = PR_IntervalToSeconds(time) % 60; \
PRUint32 msecs = PR_IntervalToMilliseconds(time) % 1000; \
printf("Time: %2d:%2d.%3d\n", mins, secs, msecs); \
}
#define A11YDEBUG_FOCUS_LOG_DOMNODE(aNode) \
if (aNode) { \
if (aNode->IsElement()) { \
dom::Element* targetElm = aNode->AsElement(); \
nsCAutoString tag; \
targetElm->Tag()->ToUTF8String(tag); \
nsCAutoString id; \
nsIAtom* atomid = targetElm->GetID(); \
if (atomid) \
atomid->ToUTF8String(id); \
printf("element %s@id='%s': %p", tag.get(), id.get(), (void*)aNode); \
} else if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) { \
nsCOMPtr<nsIDocument> document = do_QueryInterface(aNode); \
nsIURI* uri = document->GetDocumentURI(); \
nsCAutoString spec; \
uri->GetSpec(spec); \
printf("document: %p; uri: %s", (void*)aNode, spec.get()); \
} \
}
#define A11YDEBUG_FOCUS_LOG_ACCESSIBLE(aAccessible) \
printf("accessible: %p; ", (void*)aAccessible); \
if (aAccessible) { \
nsAutoString role; \
GetAccService()->GetStringRole(aAccessible->Role(), role); \
nsAutoString name; \
aAccessible->GetName(name); \
printf(" role: %s, name: %s; ", NS_ConvertUTF16toUTF8(role).get(), \
NS_ConvertUTF16toUTF8(name).get()); \
A11YDEBUG_FOCUS_LOG_DOMNODE(aAccessible->GetNode()) \
}
// Public macros
#define A11YDEBUG_FOCUS_LOG_DOMTARGET(aMsg, aTarget) \
A11YDEBUG_FOCUS_STARTBLOCK \
printf(aMsg "\n"); \
if (aTarget) { \
A11YDEBUG_FOCUS_BLOCKOFFSET \
A11YDEBUG_FOCUS_LOG_DOMNODE(aTarget) \
} \
A11YDEBUG_FOCUS_ENDBLOCK
#define A11YDEBUG_FOCUS_LOG_ACCTARGET(aMsg, aTarget) \
A11YDEBUG_FOCUS_STARTBLOCK \
printf(aMsg "\n"); \
A11YDEBUG_FOCUS_BLOCKOFFSET \
A11YDEBUG_FOCUS_LOG_ACCESSIBLE(aTarget) \
A11YDEBUG_FOCUS_ENDBLOCK
#define A11YDEBUG_FOCUS_LOG_WIDGET(aMsg, aWidget) \
A11YDEBUG_FOCUS_STARTBLOCK \
printf(aMsg "\n"); \
A11YDEBUG_FOCUS_BLOCKOFFSET \
A11YDEBUG_FOCUS_LOG_ACCESSIBLE(aWidget) \
printf("; widget is active: %s, has operable items: %s", \
(aWidget && aWidget->IsActiveWidget() ? "true" : "false"), \
(aWidget && aWidget->AreItemsOperable() ? "true" : "false")); \
A11YDEBUG_FOCUS_ENDBLOCK
#define A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET(aMsg, aTargetMsg, aTarget) \
printf("\nA11Y FOCUS: " aMsg ". "); \
A11YDEBUG_FOCUS_LOG_TIME \
if (aTarget) { \
A11YDEBUG_FOCUS_STARTBLOCK \
printf(aTargetMsg "\n"); \
A11YDEBUG_FOCUS_BLOCKOFFSET \
nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget)); \
if (targetNode) { \
A11YDEBUG_FOCUS_LOG_DOMNODE(targetNode) \
} else { \
printf("window: %p", (void*)aTarget); \
} \
A11YDEBUG_FOCUS_ENDBLOCK \
}
#define A11YDEBUG_FOCUS_NOTIFICATION_DOMTARGET(aMsg, aTargetMsg, aTarget) \
printf("\nA11Y FOCUS: " aMsg ". "); \
A11YDEBUG_FOCUS_LOG_TIME \
A11YDEBUG_FOCUS_LOG_DOMTARGET(aTargetMsg, aTarget)
#define A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET(aMsg, aTargetMsg, aTarget) \
printf("\nA11Y FOCUS: " aMsg ". "); \
A11YDEBUG_FOCUS_LOG_TIME \
A11YDEBUG_FOCUS_LOG_ACCTARGET(aTargetMsg, aTarget)
#define A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE(aMsg, aTarget) \
A11YDEBUG_FOCUS_LOG_ACCTARGET("Caused by: " aMsg, aTarget)
#else
#define A11YDEBUG_FOCUS_LOG_DOMTARGET(aMsg, aTarget)
#define A11YDEBUG_FOCUS_LOG_ACCTARGET(aMsg, aTarget)
#define A11YDEBUG_FOCUS_LOG_WIDGET(aMsg, aWidget)
#define A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET(aMsg, aTargetMsg, aTarget)
#define A11YDEBUG_FOCUS_NOTIFICATION_DOMTARGET(aMsg, aTargetMsg, aTarget)
#define A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET(aMsg, aTargetMsg, aTarget)
#define A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE(aMsg, aTarget)
#endif
#endif

View File

@ -53,6 +53,7 @@ CPPSRCS = \
AccGroupInfo.cpp \
AccIterator.cpp \
filters.cpp \
FocusManager.cpp \
NotificationController.cpp \
nsAccDocManager.cpp \
nsAccessNode.cpp \
@ -89,6 +90,7 @@ EXPORTS = \
EXPORTS_NAMESPACES = mozilla/a11y
EXPORTS_mozilla/a11y = \
FocusManager.h \
States.h \
$(NULL)

View File

@ -44,9 +44,12 @@
#include "nsDocAccessible.h"
#include "nsEventShell.h"
#include "nsTextAccessible.h"
#include "FocusManager.h"
#include "TextUpdater.h"
#include "mozilla/dom/Element.h"
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector
@ -182,7 +185,8 @@ NotificationController::IsUpdatePending()
return mPresShell->IsLayoutFlushObserver() ||
mObservingState == eRefreshProcessingForUpdate ||
mContentInsertions.Length() != 0 || mNotifications.Length() != 0 ||
mTextHash.Count() != 0;
mTextHash.Count() != 0 ||
!mDocument->HasLoadState(nsDocAccessible::eTreeConstructed);
}
////////////////////////////////////////////////////////////////////////////////
@ -312,8 +316,15 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
for (PRUint32 idx = 0; idx < eventCount; idx++) {
AccEvent* accEvent = events[idx];
if (accEvent->mEventRule != AccEvent::eDoNotEmit) {
// Dispatch the focus event if target is still focused.
if (accEvent->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
FocusMgr()->ProcessFocusEvent(accEvent);
continue;
}
mDocument->ProcessPendingEvent(accEvent);
// Fire text change event caused by tree mutation.
AccMutationEvent* showOrHideEvent = downcast_accEvent(accEvent);
if (showOrHideEvent) {
if (showOrHideEvent->mTextChangeEvent)
@ -345,14 +356,14 @@ NotificationController::CoalesceEvents()
PRInt32 tail = numQueuedEvents - 1;
AccEvent* tailEvent = mEvents[tail];
// No node means this is application accessible (which can be a subject
// of reorder events), we do not coalesce events for it currently.
if (!tailEvent->mNode)
return;
switch(tailEvent->mEventRule) {
case AccEvent::eCoalesceFromSameSubtree:
{
// No node means this is application accessible (which is a subject of
// reorder events), we do not coalesce events for it currently.
if (!tailEvent->mNode)
return;
for (PRInt32 index = tail - 1; index >= 0; index--) {
AccEvent* thisEvent = mEvents[index];
@ -446,21 +457,18 @@ NotificationController::CoalesceEvents()
} break; // case eCoalesceFromSameSubtree
case AccEvent::eCoalesceFromSameDocument:
case AccEvent::eCoalesceOfSameType:
{
// Used for focus event, coalesce more older event since focus event
// for accessible can be duplicated by event for its document, we are
// interested in focus event for accessible.
// Coalesce old events by newer event.
for (PRInt32 index = tail - 1; index >= 0; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventType == tailEvent->mEventType &&
thisEvent->mEventRule == tailEvent->mEventRule &&
thisEvent->GetDocAccessible() == tailEvent->GetDocAccessible()) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule) {
accEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eCoalesceFromSameDocument
} break; // case eCoalesceOfSameType
case AccEvent::eRemoveDupes:
{

View File

@ -418,6 +418,7 @@ nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
}
NS_LOG_ACCDOCCREATE("document creation finished", aDocument)
NS_LOG_ACCDOCCREATE_STACK
AddListeners(aDocument, isRootDoc);
return docAcc;

View File

@ -165,6 +165,8 @@ private:
*/
#ifdef DEBUG_ACCDOCMGR
#include "nsTraceRefcntImpl.h"
// Enable these to log accessible document loading, creation or destruction.
#define DEBUG_ACCDOCMGR_DOCLOAD
#define DEBUG_ACCDOCMGR_DOCCREATE
@ -405,6 +407,10 @@ private:
#define NS_LOG_ACCDOC_TEXT(aMsg) \
printf(" " aMsg "\n");
#define NS_LOG_ACCDOC_STACK \
printf(" stack: \n"); \
nsTraceRefcntImpl::WalkTheStack(stdout);
// Accessible document loading macros.
#ifdef DEBUG_ACCDOCMGR_DOCLOAD
@ -515,7 +521,10 @@ private:
NS_LOG_ACCDOC_ACCADDRESS(aName, aAcc)
#define NS_LOG_ACCDOCCREATE_TEXT(aMsg) \
NS_LOG_ACCDOC_TEXT(aMsg)
NS_LOG_ACCDOC_TEXT(aMsg)
#define NS_LOG_ACCDOCCREATE_STACK \
NS_LOG_ACCDOC_STACK
#endif // DEBUG_ACCDOCMGR_DOCCREATE
@ -559,6 +568,7 @@ private:
#define NS_LOG_ACCDOCCREATE(aMsg, aDocument)
#define NS_LOG_ACCDOCCREATE_ACCADDRESS(aName, aAcc)
#define NS_LOG_ACCDOCCREATE_TEXT(aMsg)
#define NS_LOG_ACCDOCCREATE_STACK
#endif
#ifndef DEBUG_ACCDOCMGR_DOCDESTROY

View File

@ -75,7 +75,6 @@
*/
nsIStringBundle *nsAccessNode::gStringBundle = 0;
nsINode *nsAccessNode::gLastFocusedNode = nsnull;
PRBool nsAccessNode::gIsFormFillEnabled = PR_FALSE;
@ -227,7 +226,6 @@ void nsAccessNode::ShutdownXPAccessibility()
// at exit of program
NS_IF_RELEASE(gStringBundle);
NS_IF_RELEASE(gLastFocusedNode);
// Release gApplicationAccessible after everything else is shutdown
// so we don't accidently create it again while tearing down root accessibles

View File

@ -107,11 +107,6 @@ public:
*/
nsRootAccessible* RootAccessible() const;
/**
* Reference to a node of focused accessible.
*/
static nsINode *gLastFocusedNode;
/**
* Return focused node within accessible window.
*

View File

@ -43,6 +43,8 @@
#include "nsApplicationAccessibleWrap.h"
#include "nsARIAGridAccessibleWrap.h"
#include "nsARIAMap.h"
#include "FocusManager.h"
#include "nsIContentViewer.h"
#include "nsCURILoader.h"
#include "nsDocAccessible.h"
@ -117,7 +119,8 @@ using namespace mozilla::a11y;
nsAccessibilityService *nsAccessibilityService::gAccessibilityService = nsnull;
PRBool nsAccessibilityService::gIsShutdown = PR_TRUE;
nsAccessibilityService::nsAccessibilityService() : nsAccDocManager()
nsAccessibilityService::nsAccessibilityService() :
nsAccDocManager(), FocusManager()
{
NS_TIME_FUNCTION;
}
@ -1852,3 +1855,13 @@ nsAccessibilityService::CreateAccessibleForXULTree(nsIContent* aContent,
return accessible;
}
#endif
////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////
mozilla::a11y::FocusManager*
mozilla::a11y::FocusMgr()
{
return nsAccessibilityService::gAccessibilityService;
}

View File

@ -44,9 +44,23 @@
#include "a11yGeneric.h"
#include "nsAccDocManager.h"
#include "mozilla/a11y/FocusManager.h"
#include "nsIObserver.h"
namespace mozilla {
namespace a11y {
/**
* Return focus manager.
*/
FocusManager* FocusMgr();
} // namespace a11y
} // namespace mozilla
class nsAccessibilityService : public nsAccDocManager,
public mozilla::a11y::FocusManager,
public nsIAccessibilityService,
public nsIObserver
{
@ -257,9 +271,9 @@ private:
#endif
/**
* Reference for accessibility service.
* Reference for accessibility service instance.
*/
static nsAccessibilityService *gAccessibilityService;
static nsAccessibilityService* gAccessibilityService;
/**
* Indicates whether accessibility service was shutdown.
@ -276,6 +290,7 @@ private:
PRBool HasUniversalAriaProperty(nsIContent *aContent);
friend nsAccessibilityService* GetAccService();
friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
friend nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult);
};

View File

@ -693,14 +693,12 @@ nsAccessible::NativeState()
state |= states::UNAVAILABLE;
}
else if (mContent->IsElement()) {
nsIFrame *frame = GetFrame();
if (frame && frame->IsFocusable()) {
nsIFrame* frame = GetFrame();
if (frame && frame->IsFocusable())
state |= states::FOCUSABLE;
}
if (gLastFocusedNode == mContent) {
if (FocusMgr()->IsFocused(this))
state |= states::FOCUSED;
}
}
// Check if states::INVISIBLE and
@ -746,16 +744,11 @@ nsAccessible::GetFocusedChild(nsIAccessible** aChild)
nsAccessible*
nsAccessible::FocusedChild()
{
if (!gLastFocusedNode)
return nsnull;
if (gLastFocusedNode == mContent)
return this;
nsAccessible* focus = FocusMgr()->FocusedAccessible();
if (focus && (focus == this || focus->Parent() == this))
return focus;
nsAccessible* focusedChild = GetDocAccessible()->GetAccessible(gLastFocusedNode);
if (!focusedChild || focusedChild->Parent() != this)
return nsnull;
return focusedChild;
return nsnull;
}
// nsAccessible::ChildAtPoint()
@ -1531,7 +1524,7 @@ nsAccessible::State()
nsAccessible* relTarget = nsnull;
while ((relTarget = rel.Next())) {
if (relTarget->Role() == nsIAccessibleRole::ROLE_PROPERTYPAGE &&
nsCoreUtils::IsAncestorOf(relTarget->GetNode(), gLastFocusedNode))
FocusMgr()->IsFocusWithin(relTarget))
state |= states::SELECTED;
}
}
@ -2740,6 +2733,14 @@ nsAccessible::EndOffset()
return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
}
bool
nsAccessible::IsLinkSelected()
{
NS_PRECONDITION(IsLink(),
"IsLinkSelected() called on something that is not a hyper link!");
return FocusMgr()->IsFocused(this);
}
PRUint32
nsAccessible::AnchorCount()
{
@ -2910,6 +2911,67 @@ nsAccessible::UnselectAll()
return success;
}
////////////////////////////////////////////////////////////////////////////////
// Widgets
bool
nsAccessible::IsWidget() const
{
return false;
}
bool
nsAccessible::IsActiveWidget() const
{
return FocusMgr()->IsFocused(this);
}
bool
nsAccessible::AreItemsOperable() const
{
return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant);
}
nsAccessible*
nsAccessible::CurrentItem()
{
// Check for aria-activedescendant, which changes which element has focus.
// For activedescendant, the ARIA spec does not require that the user agent
// checks whether pointed node is actually a DOM descendant of the element
// with the aria-activedescendant attribute.
nsAutoString id;
if (mContent->GetAttr(kNameSpaceID_None,
nsGkAtoms::aria_activedescendant, id)) {
nsIDocument* DOMDoc = mContent->GetOwnerDoc();
dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
if (activeDescendantElm) {
nsDocAccessible* document = GetDocAccessible();
if (document)
return document->GetAccessible(activeDescendantElm);
}
}
return nsnull;
}
nsAccessible*
nsAccessible::ContainerWidget() const
{
nsIAtom* idAttribute = mContent->GetIDAttributeName();
if (idAttribute) {
if (mContent->HasAttr(kNameSpaceID_None, idAttribute)) {
nsAccessible* parent = Parent();
do {
nsIContent* parentContent = parent->GetContent();
if (parentContent &&
parentContent->HasAttr(kNameSpaceID_None,
nsGkAtoms::aria_activedescendant)) {
return parent;
}
} while ((parent = parent->Parent()));
}
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsAccessible protected methods

View File

@ -402,10 +402,16 @@ public:
void TestChildCache(nsAccessible* aCachedChild) const;
//////////////////////////////////////////////////////////////////////////////
// Downcasting
// Downcasting and types
inline bool IsApplication() const { return mFlags & eApplicationAccessible; }
bool IsAutoComplete() const { return mFlags & eAutoCompleteAccessible; }
inline bool IsAutoCompletePopup() const { return mFlags & eAutoCompletePopupAccessible; }
inline bool IsCombobox() const { return mFlags & eComboboxAccessible; }
inline bool IsDoc() const { return mFlags & eDocAccessible; }
nsDocAccessible* AsDoc();
@ -415,6 +421,12 @@ public:
inline bool IsHTMLListItem() const { return mFlags & eHTMLListItemAccessible; }
nsHTMLLIAccessible* AsHTMLListItem();
inline bool IsListControl() const { return mFlags & eListControlAccessible; }
inline bool IsMenuButton() const { return mFlags & eMenuButtonAccessible; }
inline bool IsMenuPopup() const { return mFlags & eMenuPopupAccessible; }
inline bool IsRoot() const { return mFlags & eRootAccessible; }
nsRootAccessible* AsRoot();
@ -475,12 +487,7 @@ public:
/**
* Return true if the link currently has the focus.
*/
inline bool IsLinkSelected()
{
NS_PRECONDITION(IsLink(),
"IsLinkSelected() called on something that is not a hyper link!");
return gLastFocusedNode == GetNode();
}
bool IsLinkSelected();
/**
* Return the number of anchors within the link.
@ -546,6 +553,38 @@ public:
*/
virtual bool UnselectAll();
//////////////////////////////////////////////////////////////////////////////
// Widgets
/**
* Return true if accessible is a widget, i.e. control or accessible that
* manages its items. Note, being a widget the accessible may be a part of
* composite widget.
*/
virtual bool IsWidget() const;
/**
* Return true if the widget is active, i.e. has a focus within it.
*/
virtual bool IsActiveWidget() const;
/**
* Return true if the widget has items and items are operable by user and
* can be activated.
*/
virtual bool AreItemsOperable() const;
/**
* Return the current item of the widget, i.e. an item that has or will have
* keyboard focus when widget gets active.
*/
virtual nsAccessible* CurrentItem();
/**
* Return container widget this accessible belongs to.
*/
virtual nsAccessible* ContainerWidget() const;
protected:
//////////////////////////////////////////////////////////////////////////////
@ -595,11 +634,17 @@ protected:
*/
enum AccessibleTypes {
eApplicationAccessible = 1 << 2,
eDocAccessible = 1 << 3,
eHyperTextAccessible = 1 << 4,
eHTMLListItemAccessible = 1 << 5,
eRootAccessible = 1 << 6,
eTextLeafAccessible = 1 << 7
eAutoCompleteAccessible = 1 << 3,
eAutoCompletePopupAccessible = 1 << 4,
eComboboxAccessible = 1 << 5,
eDocAccessible = 1 << 6,
eHyperTextAccessible = 1 << 7,
eHTMLListItemAccessible = 1 << 8,
eListControlAccessible = 1 << 9,
eMenuButtonAccessible = 1 << 10,
eMenuPopupAccessible = 1 << 11,
eRootAccessible = 1 << 12,
eTextLeafAccessible = 1 << 13
};
//////////////////////////////////////////////////////////////////////////////

View File

@ -174,12 +174,10 @@ nsApplicationAccessible::ChildAtPoint(PRInt32 aX, PRInt32 aY,
nsAccessible*
nsApplicationAccessible::FocusedChild()
{
if (gLastFocusedNode) {
nsAccessible* focusedChild =
GetAccService()->GetAccessible(gLastFocusedNode);
if (focusedChild && focusedChild->Parent() == this)
return focusedChild;
}
nsAccessible* focus = FocusMgr()->FocusedAccessible();
if (focus && focus->Parent() == this)
return focus;
return nsnull;
}

View File

@ -295,18 +295,10 @@ nsDocAccessible::NativeState()
PRUint64 state = (mContent->GetCurrentDoc() == mDocument) ?
0 : states::STALE;
#ifdef MOZ_XUL
nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocument));
if (!xulDoc)
#endif
{
// XXX Need to invent better check to see if doc is focusable,
// which it should be if it is scrollable. A XUL document could be focusable.
// See bug 376803.
state |= states::FOCUSABLE;
if (gLastFocusedNode == mDocument)
state |= states::FOCUSED;
}
// Document is always focusable.
state |= states::FOCUSABLE;
if (FocusMgr()->IsFocused(this))
state |= states::FOCUSED;
// Expose stale state until the document is ready (DOM is loaded and tree is
// constructed).
@ -357,12 +349,9 @@ nsDocAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
nsAccessible*
nsDocAccessible::FocusedChild()
{
// XXXndeakin P3 accessibility shouldn't be caching the focus
// Return an accessible for the current global focus, which does not have to
// be contained within the current document.
return gLastFocusedNode ? GetAccService()->GetAccessible(gLastFocusedNode) :
nsnull;
return FocusMgr()->FocusedAccessible();
}
NS_IMETHODIMP nsDocAccessible::TakeFocus()
@ -370,11 +359,6 @@ NS_IMETHODIMP nsDocAccessible::TakeFocus()
if (IsDefunct())
return NS_ERROR_FAILURE;
PRUint64 state = NativeState();
if (0 == (state & states::FOCUSABLE)) {
return NS_ERROR_FAILURE; // Not focusable
}
// Focus the document.
nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
NS_ENSURE_STATE(fm);
@ -1063,11 +1047,7 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
nsAccessible *multiSelect =
nsAccUtils::GetMultiSelectableContainer(aContent);
// Multi selects use selection_add and selection_remove
// Single select widgets just mirror event_selection for
// whatever gets event_focus, which is done in
// nsRootAccessible::FireAccessibleFocusEvent()
// So right here we make sure only to deal with multi selects
// XXX: Multi selects are handled here only (bug 414302).
if (multiSelect) {
// Need to find the right event to use here, SELECTION_WITHIN would
// seem right but we had started using it for something else
@ -1118,17 +1098,13 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
return;
}
// The activedescendant universal property redirects accessible focus events
// to the element with the id that activedescendant points to. Make sure
// the tree up to date before processing.
if (aAttribute == nsGkAtoms::aria_activedescendant) {
// The activedescendant universal property redirects accessible focus events
// to the element with the id that activedescendant points to
nsCOMPtr<nsINode> focusedNode = GetCurrentFocus();
if (nsCoreUtils::GetRoleContent(focusedNode) == aContent) {
nsAccessible* focusedAcc = GetAccService()->GetAccessible(focusedNode);
nsRootAccessible* rootAcc = RootAccessible();
if (rootAcc && focusedAcc) {
rootAcc->FireAccessibleFocusEvent(focusedAcc, nsnull, PR_TRUE);
}
}
mNotificationController->HandleNotification<nsDocAccessible, nsIContent>
(this, &nsDocAccessible::ARIAActiveDescendantChanged, aContent);
return;
}
@ -1200,6 +1176,26 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
}
}
void
nsDocAccessible::ARIAActiveDescendantChanged(nsIContent* aElm)
{
if (FocusMgr()->HasDOMFocus(aElm)) {
nsAutoString id;
if (aElm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
nsIDocument* DOMDoc = aElm->GetOwnerDoc();
dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
if (activeDescendantElm) {
nsAccessible* activeDescendant = GetAccessible(activeDescendantElm);
if (activeDescendant) {
FocusMgr()->ActiveItemChanged(activeDescendant, false);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("ARIA activedescedant changed",
activeDescendant)
}
}
}
}
}
void nsDocAccessible::ContentAppended(nsIDocument *aDocument,
nsIContent* aContainer,
nsIContent* aFirstNewContent,
@ -1362,6 +1358,13 @@ nsDocAccessible::UnbindFromDocument(nsAccessible* aAccessible)
NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
"Unbinding the unbound accessible!");
// Fire focus event on accessible having DOM focus if active item was removed
// from the tree.
if (FocusMgr()->IsActiveItem(aAccessible)) {
FocusMgr()->ActiveItemChanged(nsnull);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("tree shutdown", aAccessible)
}
// Remove an accessible from node-to-accessible map if it exists there.
if (aAccessible->IsPrimaryForNode() &&
mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
@ -1732,7 +1735,6 @@ nsDocAccessible::ProcessPendingEvent(AccEvent* aEvent)
return;
PRUint32 eventType = aEvent->GetEventType();
if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
nsCOMPtr<nsIAccessibleText> accessibleText = do_QueryObject(accessible);
PRInt32 caretOffset;
@ -1742,13 +1744,6 @@ nsDocAccessible::ProcessPendingEvent(AccEvent* aEvent)
PRUnichar chAtOffset;
accessibleText->GetCharacterAtOffset(caretOffset, &chAtOffset);
printf("\nCaret moved to %d with char %c", caretOffset, chAtOffset);
#endif
#ifdef DEBUG_CARET
// Test caret line # -- fire an EVENT_ALERT on the focused node so we can watch the
// line-number object attribute on it
nsAccessible* focusedAcc =
GetAccService()->GetAccessible(gLastFocusedNode);
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, focusedAcc);
#endif
nsRefPtr<AccEvent> caretMoveEvent =
new AccCaretMoveEvent(accessible, caretOffset);
@ -1948,10 +1943,11 @@ nsDocAccessible::UpdateTreeInternal(nsAccessible* aChild, bool aIsInsert)
// while it's focused. Fire focus event on new focused accessible. If
// the queue contains focus event for this node then it's suppressed by
// this one.
if (node == gLastFocusedNode) {
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS,
node, AccEvent::eCoalesceFromSameDocument);
}
// XXX: do we really want to send focus to focused DOM node not taking into
// account active item?
if (FocusMgr()->IsFocused(aChild))
FocusMgr()->DispatchFocusEvent(this, aChild);
} else {
// Update the tree for content removal.
// The accessible parent may differ from container accessible if

View File

@ -456,6 +456,11 @@ protected:
*/
void ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute);
/**
* Process ARIA active-descendant attribute change.
*/
void ARIAActiveDescendantChanged(nsIContent* aElm);
/**
* Process the event when the queue of pending events is untwisted. Fire
* accessible events as result of the processing.

View File

@ -69,12 +69,6 @@ nsOuterDocAccessible::NativeRole()
return nsIAccessibleRole::ROLE_INTERNAL_FRAME;
}
PRUint64
nsOuterDocAccessible::NativeState()
{
return nsAccessible::NativeState() & ~states::FOCUSABLE;
}
nsAccessible*
nsOuterDocAccessible::ChildAtPoint(PRInt32 aX, PRInt32 aY,
EWhichChildAtPoint aWhichChild)

View File

@ -67,7 +67,6 @@ public:
// nsAccessible
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
virtual nsAccessible* ChildAtPoint(PRInt32 aX, PRInt32 aY,
EWhichChildAtPoint aWhichChild);

View File

@ -61,14 +61,11 @@
#include "nsIDOMHTMLSelectElement.h"
#include "nsIDOMDataContainerEvent.h"
#include "nsIDOMNSEvent.h"
#include "nsIDOMXULMenuListElement.h"
#include "nsIDOMXULMultSelectCntrlEl.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
#include "nsIDOMXULPopupElement.h"
#include "nsIDocument.h"
#include "nsEventListenerManager.h"
#include "nsIFrame.h"
#include "nsMenuFrame.h"
#include "nsIHTMLDocument.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsISelectionPrivate.h"
@ -86,7 +83,7 @@
#include "nsIXULWindow.h"
#endif
using namespace mozilla;
namespace dom = mozilla::dom;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
@ -223,9 +220,6 @@ const char* const docEvents[] = {
// debugging a11y objects with event viewers
"mouseover",
#endif
// capture DOM focus and DOM blur events
"focus",
"blur",
// capture Form change events
"select",
// capture ValueChange events (fired whenever value changes, immediately after, whether focus moves or not)
@ -246,6 +240,7 @@ const char* const docEvents[] = {
"popuphiding",
"DOMMenuInactive",
"DOMMenuItemActive",
"DOMMenuItemInactive",
"DOMMenuBarActive",
"DOMMenuBarInactive"
};
@ -308,125 +303,6 @@ nsRootAccessible::GetCaretAccessible()
return mCaretAccessible;
}
void
nsRootAccessible::FireAccessibleFocusEvent(nsAccessible* aFocusAccessible,
nsIContent* aRealFocusContent,
PRBool aForceEvent,
EIsFromUserInput aIsFromUserInput)
{
// Implementors: only fire delayed/async events from this method.
// Set selection listener for focused element.
if (mCaretAccessible && aRealFocusContent)
mCaretAccessible->SetControlSelectionListener(aRealFocusContent);
nsAccessible* focusAccessible = aFocusAccessible;
// Check for aria-activedescendant, which changes which element has focus.
// For activedescendant, the ARIA spec does not require that the user agent
// checks whether pointed node is actually a DOM descendant of the element
// with the aria-activedescendant attribute.
nsIContent* content = focusAccessible->GetContent();
if (content) {
nsAutoString id;
if (content->GetAttr(kNameSpaceID_None,
nsGkAtoms::aria_activedescendant, id)) {
nsIDocument* DOMDoc = content->GetOwnerDoc();
nsIContent* activeDescendantContent = DOMDoc->GetElementById(id);
// If aria-activedescendant is set to nonexistant ID, then treat as focus
// on the activedescendant container (which has real DOM focus).
if (activeDescendantContent) {
nsAccessible* activeDescendant =
GetAccService()->GetAccessible(activeDescendantContent);
if (activeDescendant) {
focusAccessible = activeDescendant;
}
}
}
}
// Fire focus only if it changes, but always fire focus events when
// aForceEvent == PR_TRUE
nsINode* focusNode = focusAccessible->GetNode();
if (gLastFocusedNode == focusNode && !aForceEvent)
return;
nsDocAccessible* focusDocument = focusAccessible->GetDocAccessible();
NS_ASSERTION(focusDocument, "No document while accessible is in document?!");
// Fire menu start/end events for ARIA menus.
if (focusAccessible->ARIARole() == nsIAccessibleRole::ROLE_MENUITEM) {
// The focus is inside a menu.
if (!mCurrentARIAMenubar) {
// Entering ARIA menu. Fire menu start event.
nsAccessible* menuBarAccessible =
nsAccUtils::GetAncestorWithRole(focusAccessible,
nsIAccessibleRole::ROLE_MENUBAR);
if (menuBarAccessible) {
mCurrentARIAMenubar = menuBarAccessible->GetNode();
if (mCurrentARIAMenubar) {
nsRefPtr<AccEvent> menuStartEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
menuBarAccessible, aIsFromUserInput,
AccEvent::eAllowDupes);
if (menuStartEvent)
focusDocument->FireDelayedAccessibleEvent(menuStartEvent);
}
}
}
}
else if (mCurrentARIAMenubar) {
// Focus left a menu. Fire menu end event.
nsRefPtr<AccEvent> menuEndEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mCurrentARIAMenubar,
aIsFromUserInput, AccEvent::eAllowDupes);
if (menuEndEvent) {
focusDocument->FireDelayedAccessibleEvent(menuEndEvent);
}
mCurrentARIAMenubar = nsnull;
}
NS_IF_RELEASE(gLastFocusedNode);
gLastFocusedNode = focusNode;
NS_IF_ADDREF(gLastFocusedNode);
// Coalesce focus events from the same document, because DOM focus event might
// be fired for the document node and then for the focused DOM element.
nsRefPtr<AccEvent> focusEvent =
new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, focusAccessible,
aIsFromUserInput, AccEvent::eCoalesceFromSameDocument);
focusDocument->FireDelayedAccessibleEvent(focusEvent);
}
void
nsRootAccessible::FireCurrentFocusEvent()
{
if (IsDefunct())
return;
// Simulate a focus event so that we can reuse code that fires focus for
// container children like treeitems.
nsCOMPtr<nsINode> focusedNode = GetCurrentFocus();
if (!focusedNode) {
return; // No current focus
}
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
if (domDoc) {
nsCOMPtr<nsIDOMEvent> event;
if (NS_SUCCEEDED(domDoc->CreateEvent(NS_LITERAL_STRING("Events"),
getter_AddRefs(event))) &&
NS_SUCCEEDED(event->InitEvent(NS_LITERAL_STRING("focus"), PR_TRUE, PR_TRUE))) {
nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(event));
nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(focusedNode));
privateEvent->SetTarget(target);
HandleEvent(event);
}
}
}
void
nsRootAccessible::DocumentActivated(nsDocAccessible* aDocument)
{
@ -497,14 +373,13 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
if (!weakShell)
return;
nsAccessible* accessible =
GetAccService()->GetAccessibleOrContainer(origTargetNode, weakShell);
if (eventType.EqualsLiteral("popuphiding")) {
HandlePopupHidingEvent(origTargetNode, accessible);
HandlePopupHidingEvent(origTargetNode);
return;
}
nsAccessible* accessible =
GetAccService()->GetAccessibleOrContainer(origTargetNode, weakShell);
if (!accessible)
return;
@ -559,8 +434,10 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
nsEventShell::FireEvent(accEvent);
if (isEnabled)
FireAccessibleFocusEvent(accessible, origTargetContent);
if (isEnabled) {
FocusMgr()->ActiveItemChanged(accessible);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("RadioStateChange", accessible)
}
return;
}
@ -581,20 +458,9 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
#ifdef MOZ_XUL
// If it's a tree element, need the currently selected item
if (isTree) {
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
do_QueryInterface(targetNode);
if (multiSelect) {
PRInt32 treeIndex = -1;
multiSelect->GetCurrentIndex(&treeIndex);
if (treeIndex >= 0) {
nsRefPtr<nsXULTreeAccessible> treeAcc = do_QueryObject(accessible);
if (treeAcc) {
treeItemAccessible = treeAcc->GetTreeItemAccessible(treeIndex);
if (treeItemAccessible)
accessible = treeItemAccessible;
}
}
}
treeItemAccessible = accessible->CurrentItem();
if (treeItemAccessible)
accessible = treeItemAccessible;
}
#endif
@ -611,7 +477,7 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
if (treeItemAccessible && eventType.EqualsLiteral("select")) {
// If multiselect tree, we should fire selectionadd or selection removed
if (gLastFocusedNode == targetNode) {
if (FocusMgr()->HasDOMFocus(targetNode)) {
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
do_QueryInterface(targetNode);
nsAutoString selType;
@ -633,41 +499,7 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
}
else
#endif
if (eventType.EqualsLiteral("focus")) {
// Keep a reference to the target node. We might want to change
// it to the individual radio button or selected item, and send
// the focus event to that.
nsCOMPtr<nsINode> focusedItem = targetNode;
if (!treeItemAccessible) {
nsCOMPtr<nsIDOMXULSelectControlElement> selectControl =
do_QueryInterface(targetNode);
if (selectControl) {
nsCOMPtr<nsIDOMXULMenuListElement> menuList =
do_QueryInterface(targetNode);
if (!menuList) {
// Don't do this for menu lists, the items only get focused
// when the list is open, based on DOMMenuitemActive events
nsCOMPtr<nsIDOMXULSelectControlItemElement> selectedItem;
selectControl->GetSelectedItem(getter_AddRefs(selectedItem));
if (selectedItem)
focusedItem = do_QueryInterface(selectedItem);
if (!focusedItem)
return;
accessible = GetAccService()->GetAccessibleInWeakShell(focusedItem,
weakShell);
if (!accessible)
return;
}
}
}
FireAccessibleFocusEvent(accessible, origTargetContent);
}
else if (eventType.EqualsLiteral("blur")) {
NS_IF_RELEASE(gLastFocusedNode);
}
else if (eventType.EqualsLiteral("AlertActive")) {
if (eventType.EqualsLiteral("AlertActive")) {
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible);
}
else if (eventType.EqualsLiteral("popupshown")) {
@ -680,70 +512,43 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
}
}
else if (eventType.EqualsLiteral("DOMMenuItemActive")) {
PRBool fireFocus = PR_FALSE;
if (!treeItemAccessible) {
#ifdef MOZ_XUL
if (isTree) {
return; // Tree with nothing selected
}
#endif
nsMenuFrame* menuFrame = do_QueryFrame(accessible->GetFrame());
if (menuFrame)
fireFocus = PR_TRUE;
// QI failed for nsMenuFrame means it's not on menu bar
if (menuFrame && menuFrame->IsOnMenuBar() &&
!menuFrame->IsOnActiveMenuBar()) {
// It is a top level menuitem. Only fire a focus event when the menu bar
// is active.
return;
} else {
nsAccessible* container = accessible->Parent();
if (!container)
return;
// It is not top level menuitem
// Only fire focus event if it is not inside collapsed popup
// and not a listitem of a combo box
if (container->State() & states::COLLAPSED) {
nsAccessible* containerParent = container->Parent();
if (!containerParent)
return;
if (containerParent->Role() != nsIAccessibleRole::ROLE_COMBOBOX) {
return;
}
}
}
}
if (!fireFocus) {
nsCOMPtr<nsINode> realFocusedNode = GetCurrentFocus();
nsIContent* realFocusedContent =
realFocusedNode->IsElement() ? realFocusedNode->AsElement() : nsnull;
nsIContent* containerContent = targetContent;
while (containerContent) {
nsCOMPtr<nsIDOMXULPopupElement> popup = do_QueryInterface(containerContent);
if (popup || containerContent == realFocusedContent) {
// If we're inside the focus or a popup we can fire focus events
// for the changed active item
fireFocus = PR_TRUE;
break;
}
containerContent = containerContent->GetParent();
}
}
if (fireFocus) {
// Always asynch, always from user input.
FireAccessibleFocusEvent(accessible, origTargetContent, PR_TRUE,
eFromUserInput);
FocusMgr()->ActiveItemChanged(accessible);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("DOMMenuItemActive", accessible)
}
else if (eventType.EqualsLiteral("DOMMenuItemInactive")) {
// Process DOMMenuItemInactive event for autocomplete only because this is
// unique widget that may acquire focus from autocomplete popup while popup
// stays open and has no active item. In case of XUL tree autocomplete
// popup this event is fired for tree accessible.
nsAccessible* widget =
accessible->IsWidget() ? accessible : accessible->ContainerWidget();
if (widget && widget->IsAutoCompletePopup()) {
FocusMgr()->ActiveItemChanged(nsnull);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("DOMMenuItemInactive", accessible)
}
}
else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START,
accessible, eFromUserInput);
// Notify of active item change when menubar gets active and if it has
// current item. This is a case of mouseover (set current menuitem) and
// mouse click (activate the menubar). If menubar doesn't have current item
// (can be a case of menubar activation from keyboard) then ignore this
// notification because later we'll receive DOMMenuItemActive event after
// current menuitem is set.
nsAccessible* activeItem = accessible->CurrentItem();
if (activeItem) {
FocusMgr()->ActiveItemChanged(activeItem);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("DOMMenuBarActive", accessible)
}
}
else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END,
accessible, eFromUserInput);
FireCurrentFocusEvent();
FocusMgr()->ActiveItemChanged(nsnull);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("DOMMenuBarInactive", accessible)
}
else if (eventType.EqualsLiteral("ValueChange")) {
targetDocument->
@ -769,8 +574,6 @@ nsRootAccessible::Shutdown()
if (!mWeakShell)
return; // Already shutdown
mCurrentARIAMenubar = nsnull;
nsDocAccessibleWrap::Shutdown();
}
@ -886,37 +689,106 @@ nsRootAccessible::HandlePopupShownEvent(nsAccessible* aAccessible)
}
void
nsRootAccessible::HandlePopupHidingEvent(nsINode* aNode,
nsAccessible* aAccessible)
nsRootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode)
{
// If accessible focus was on or inside popup that closes, then restore it
// to true current focus. This is the case when we've been getting
// DOMMenuItemActive events inside of a combo box that closes. The real focus
// is on the combo box. It's also the case when a popup gets focus in ATK --
// when it closes we need to fire an event to restore focus to where it was.
// Get popup accessible. There are cases when popup element isn't accessible
// but an underlying widget is and behaves like popup, an example is
// autocomplete popups.
nsDocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode);
if (!document)
return;
if (gLastFocusedNode &&
nsCoreUtils::IsAncestorOf(aNode, gLastFocusedNode)) {
// Focus was on or inside of a popup that's being hidden
FireCurrentFocusEvent();
nsAccessible* popup = document->GetAccessible(aPopupNode);
if (!popup) {
nsAccessible* popupContainer = document->GetContainerAccessible(aPopupNode);
if (!popupContainer)
return;
PRInt32 childCount = popupContainer->GetChildCount();
for (PRInt32 idx = 0; idx < childCount; idx++) {
nsAccessible* child = popupContainer->GetChildAt(idx);
if (child->IsAutoCompletePopup()) {
popup = child;
break;
}
}
// No popup no events. Focus is managed by DOM. This is a case for
// menupopups of menus on Linux since there are no accessible for popups.
if (!popup)
return;
}
// Fire expanded state change event for comboboxes and autocompletes.
if (!aAccessible ||
aAccessible->Role() != nsIAccessibleRole::ROLE_COMBOBOX_LIST)
return;
// In case of autocompletes and comboboxes fire state change event for
// expanded state. Note, HTML form autocomplete isn't a subject of state
// change event because they aren't autocompletes strictly speaking.
// When popup closes (except nested popups and menus) then fire focus event to
// where it was. The focus event is expected even if popup didn't take a focus.
nsAccessible* combobox = aAccessible->Parent();
if (!combobox)
return;
static const PRUint32 kNotifyOfFocus = 1;
static const PRUint32 kNotifyOfState = 2;
PRUint32 notifyOf = 0;
PRUint32 comboboxRole = combobox->Role();
if (comboboxRole == nsIAccessibleRole::ROLE_COMBOBOX ||
comboboxRole == nsIAccessibleRole::ROLE_AUTOCOMPLETE) {
// HTML select is target of popuphidding event. Otherwise get container
// widget. No container widget means this is either tooltip or menupopup.
// No events in the former case.
nsAccessible* widget = nsnull;
if (popup->IsCombobox()) {
widget = popup;
} else {
widget = popup->ContainerWidget();
if (!widget) {
if (!popup->IsMenuPopup())
return;
widget = popup;
}
}
if (popup->IsAutoCompletePopup()) {
// No focus event for autocomplete because it's managed by
// DOMMenuItemInactive events.
if (widget->IsAutoComplete())
notifyOf = kNotifyOfState;
} else if (widget->IsCombobox()) {
// Fire focus for active combobox, otherwise the focus is managed by DOM
// focus notifications. Always fire state change event.
if (widget->IsActiveWidget())
notifyOf = kNotifyOfFocus;
notifyOf |= kNotifyOfState;
} else if (widget->IsMenuButton()) {
// Can be a part of autocomplete.
nsAccessible* compositeWidget = widget->ContainerWidget();
if (compositeWidget && compositeWidget->IsAutoComplete()) {
widget = compositeWidget;
notifyOf = kNotifyOfState;
}
// Autocomplete (like searchbar) can be inactive when popup hiddens
notifyOf |= kNotifyOfFocus;
} else if (widget == popup) {
// Top level context menus and alerts.
// Ignore submenus and menubar. When submenu is closed then sumbenu
// container menuitem takes a focus via DOMMenuItemActive notification.
// For menubars processing we listen DOMMenubarActive/Inactive
// notifications.
notifyOf = kNotifyOfFocus;
}
// Restore focus to where it was.
if (notifyOf & kNotifyOfFocus) {
FocusMgr()->ActiveItemChanged(nsnull);
A11YDEBUG_FOCUS_ACTIVEITEMCHANGE_CAUSE("popuphiding", popup)
}
// Fire expanded state change event.
if (notifyOf & kNotifyOfState) {
nsRefPtr<AccEvent> event =
new AccStateChangeEvent(combobox, states::EXPANDED, PR_FALSE);
if (event)
nsEventShell::FireEvent(event);
new AccStateChangeEvent(widget, states::EXPANDED, PR_FALSE);
document->FireDelayedAccessibleEvent(event);
}
}

View File

@ -90,38 +90,6 @@ public:
// nsRootAccessible
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ROOTACCESSIBLE_IMPL_CID)
/**
* Fire an accessible focus event for the focused accessible and attach a new
* selection listener to real focused element, if necessary.
*
* @param aFocusAccessible [in] the accessible which has received focus
* @param aRealFocusContent [in] the actual DOM element which has received
* focus (see @note section)
* @param aForceEvent [in, optional] fire a focus event even if
* the last focused item was the same
* @param aIsFromUserInput [in, optional] specifies whether the event is
* from user input
*
* @note Use the originally focused node where the selection lives as real
* focus node. For example, use the anonymous HTML:input instead of
* the containing XUL:textbox. In this case, sometimes it is a later
* focus event which points to the actual anonymous child with focus,
* so to be safe we need to reset the selection listener every time.
* This happens because when some bindings handle focus, they
* retarget focus to the appropriate child inside of themselves, but
* DOM focus stays outside on that binding parent.
*/
void FireAccessibleFocusEvent(nsAccessible* aFocusAccessible,
nsIContent* aRealFocusContent,
PRBool aForceEvent = PR_FALSE,
EIsFromUserInput aIsFromUserInput = eAutoDetect);
/**
* Fire an accessible focus event for the current focused node,
* if there is a focus.
*/
void FireCurrentFocusEvent();
nsCaretAccessible *GetCaretAccessible();
/**
@ -130,7 +98,6 @@ public:
virtual void DocumentActivated(nsDocAccessible* aDocument);
protected:
NS_DECL_RUNNABLEMETHOD(nsRootAccessible, FireCurrentFocusEvent)
/**
* Add/remove DOM event listeners.
@ -151,7 +118,7 @@ protected:
/*
* Process "popuphiding" event. Used by HandleEvent().
*/
void HandlePopupHidingEvent(nsINode* aNode, nsAccessible* aAccessible);
void HandlePopupHidingEvent(nsINode* aNode);
#ifdef MOZ_XUL
void HandleTreeRowCountChangedEvent(nsIDOMEvent* aEvent,
@ -164,7 +131,6 @@ protected:
already_AddRefed<nsIDocShellTreeItem>
GetContentDocShell(nsIDocShellTreeItem *aStart);
nsRefPtr<nsCaretAccessible> mCaretAccessible;
nsCOMPtr<nsINode> mCurrentARIAMenubar;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsRootAccessible, NS_ROOTACCESSIBLE_IMPL_CID)

View File

@ -463,40 +463,29 @@ nsHTMLTextFieldAccessible::NativeState()
nsGkAtoms::password, eIgnoreCase)) {
state |= states::PROTECTED;
}
else {
nsAccessible* parent = Parent();
if (parent && parent->Role() == nsIAccessibleRole::ROLE_AUTOCOMPLETE)
state |= states::HASPOPUP;
}
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) {
state |= states::READONLY;
}
nsCOMPtr<nsIDOMHTMLInputElement> htmlInput(do_QueryInterface(mContent));
// Is it an <input> or a <textarea> ?
if (htmlInput) {
state |= states::SINGLE_LINE;
}
else {
state |= states::MULTI_LINE;
}
nsCOMPtr<nsIDOMHTMLInputElement> htmlInput(do_QueryInterface(mContent));
state |= htmlInput ? states::SINGLE_LINE : states::MULTI_LINE;
if (!(state & states::EDITABLE))
if (!(state & states::EDITABLE) ||
(state & (states::PROTECTED | states::MULTI_LINE)))
return state;
nsCOMPtr<nsIContent> bindingContent = mContent->GetBindingParent();
if (bindingContent &&
bindingContent->NodeInfo()->Equals(nsGkAtoms::textbox,
kNameSpaceID_XUL)) {
if (bindingContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::autocomplete,
eIgnoreCase)) {
// If parent is XUL textbox and value of @type attribute is "autocomplete",
// then this accessible supports autocompletion.
state |= states::SUPPORTS_AUTOCOMPLETION;
}
} else if (gIsFormFillEnabled && htmlInput && !(state & states::PROTECTED)) {
// Expose autocomplete states if this input is part of autocomplete widget.
nsAccessible* widget = ContainerWidget();
if (widget && widget-IsAutoComplete()) {
state |= states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION;
return state;
}
// No parent can mean a fake widget created for XUL textbox. If accessible
// is unattached from tree then we don't care.
if (mParent && gIsFormFillEnabled) {
// Check to see if autocompletion is allowed on this input. We don't expose
// it for password fields even though the entire password can be remembered
// for a page if the user asks it to be. However, the kind of autocomplete
@ -575,6 +564,23 @@ NS_IMETHODIMP nsHTMLTextFieldAccessible::GetAssociatedEditor(nsIEditor **aEditor
return rv;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLTextFieldAccessible: Widgets
bool
nsHTMLTextFieldAccessible::IsWidget() const
{
return true;
}
nsAccessible*
nsHTMLTextFieldAccessible::ContainerWidget() const
{
return mParent && mParent->Role() == nsIAccessibleRole::ROLE_AUTOCOMPLETE ?
mParent : nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLGroupboxAccessible
////////////////////////////////////////////////////////////////////////////////

View File

@ -166,6 +166,10 @@ public:
// ActionAccessible
virtual PRUint8 ActionCount();
// Widgets
virtual bool IsWidget() const;
virtual nsAccessible* ContainerWidget() const;
};

View File

@ -67,6 +67,7 @@ nsHTMLSelectListAccessible::
nsHTMLSelectListAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsAccessibleWrap(aContent, aShell)
{
mFlags |= eListControlAccessible;
}
////////////////////////////////////////////////////////////////////////////////
@ -76,19 +77,6 @@ PRUint64
nsHTMLSelectListAccessible::NativeState()
{
PRUint64 state = nsAccessibleWrap::NativeState();
// As a nsHTMLSelectListAccessible we can have the following states:
// states::MULTISELECTABLE, states::EXTSELECTABLE
if (state & states::FOCUSED) {
// Treat first focusable option node as actual focus, in order
// to avoid confusing JAWS, which needs focus on the option
nsCOMPtr<nsIContent> focusedOption =
nsHTMLSelectOptionAccessible::GetFocusedOption(mContent);
if (focusedOption) { // Clear focused state since it is on option
state &= ~states::FOCUSED;
}
}
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple))
state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
@ -127,6 +115,42 @@ nsHTMLSelectListAccessible::UnselectAll()
nsAccessibleWrap::UnselectAll() : false;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLSelectListAccessible: Widgets
bool
nsHTMLSelectListAccessible::IsWidget() const
{
return true;
}
bool
nsHTMLSelectListAccessible::IsActiveWidget() const
{
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsHTMLSelectListAccessible::AreItemsOperable() const
{
return true;
}
nsAccessible*
nsHTMLSelectListAccessible::CurrentItem()
{
nsIListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
if (listControlFrame) {
nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
if (activeOptionNode) {
nsDocAccessible* document = GetDocAccessible();
if (document)
return document->GetAccessible(activeOptionNode);
}
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLSelectListAccessible: nsAccessible protected
@ -252,25 +276,12 @@ nsHTMLSelectOptionAccessible::NativeState()
PRUint64 selectState = 0;
nsIContent* selectContent = GetSelectState(&selectState);
if (selectState & states::INVISIBLE)
if (!selectContent || selectState & states::INVISIBLE)
return state;
NS_ENSURE_TRUE(selectContent, NS_ERROR_FAILURE);
// Is disabled?
if (0 == (state & states::UNAVAILABLE)) {
// Focusable and selectable
if (!(state & states::UNAVAILABLE))
state |= (states::FOCUSABLE | states::SELECTABLE);
// When the list is focused but no option is actually focused,
// Firefox draws a focus ring around the first non-disabled option.
// We need to indicate states::FOCUSED in that case, because it
// prevents JAWS from ignoring the list
// GetFocusedOption() ensures that an option node is
// returned in this case, as long as some focusable option exists
// in the listbox
nsCOMPtr<nsIContent> focusedOption = GetFocusedOption(selectContent);
if (focusedOption == mContent)
state |= states::FOCUSED;
}
// Are we selected?
PRBool isSelected = PR_FALSE;
@ -279,7 +290,6 @@ nsHTMLSelectOptionAccessible::NativeState()
option->GetSelected(&isSelected);
if (isSelected)
state |= states::SELECTED;
}
if (selectState & states::OFFSCREEN) {
@ -377,62 +387,17 @@ nsHTMLSelectOptionAccessible::ActionCount()
return 1;
}
NS_IMETHODIMP nsHTMLSelectOptionAccessible::DoAction(PRUint8 index)
NS_IMETHODIMP
nsHTMLSelectOptionAccessible::DoAction(PRUint8 aIndex)
{
if (index == eAction_Select) { // default action
nsCOMPtr<nsIDOMHTMLOptionElement> newHTMLOption(do_QueryInterface(mContent));
if (!newHTMLOption)
return NS_ERROR_FAILURE;
// Clear old selection
nsAccessible* parent = Parent();
if (!parent)
return NS_OK;
if (aIndex != eAction_Select)
return NS_ERROR_INVALID_ARG;
nsCOMPtr<nsIContent> oldHTMLOptionContent =
GetFocusedOption(parent->GetContent());
nsCOMPtr<nsIDOMHTMLOptionElement> oldHTMLOption =
do_QueryInterface(oldHTMLOptionContent);
if (oldHTMLOption)
oldHTMLOption->SetSelected(PR_FALSE);
// Set new selection
newHTMLOption->SetSelected(PR_TRUE);
if (IsDefunct())
return NS_ERROR_FAILURE;
// If combo box, and open, close it
// First, get the <select> widgets list control frame
nsIContent *selectContent = mContent;
do {
selectContent = selectContent->GetParent();
nsCOMPtr<nsIDOMHTMLSelectElement> selectControl =
do_QueryInterface(selectContent);
if (selectControl)
break;
} while (selectContent);
nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(mContent));
if (!selectContent || !presShell || !option)
return NS_ERROR_FAILURE;
nsIFrame *selectFrame = selectContent->GetPrimaryFrame();
nsIComboboxControlFrame *comboBoxFrame = do_QueryFrame(selectFrame);
if (comboBoxFrame) {
nsIFrame *listFrame = comboBoxFrame->GetDropDown();
if (comboBoxFrame->IsDroppedDown() && listFrame) {
// use this list control frame to roll up the list
nsIListControlFrame *listControlFrame = do_QueryFrame(listFrame);
if (listControlFrame) {
PRInt32 newIndex = 0;
option->GetIndex(&newIndex);
listControlFrame->ComboboxFinish(newIndex);
}
}
}
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
DoCommand();
return NS_OK;
}
NS_IMETHODIMP
@ -446,73 +411,24 @@ nsHTMLSelectOptionAccessible::SetSelected(PRBool aSelect)
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLSelectOptionAccessible: static methods
// nsHTMLSelectOptionAccessible: Widgets
/**
* Helper method for getting the focused DOM Node from our parent(list) node. We
* need to use the frame to get the focused option because for some reason we
* weren't getting the proper notification when the focus changed using the DOM
*/
already_AddRefed<nsIContent>
nsHTMLSelectOptionAccessible::GetFocusedOption(nsIContent *aListNode)
nsAccessible*
nsHTMLSelectOptionAccessible::ContainerWidget() const
{
NS_ASSERTION(aListNode, "Called GetFocusedOptionNode without a valid list node");
if (mParent && mParent->IsListControl()) {
nsAccessible* grandParent = mParent->Parent();
if (grandParent && grandParent->IsCombobox())
return grandParent;
nsIFrame *frame = aListNode->GetPrimaryFrame();
if (!frame)
return nsnull;
PRInt32 focusedOptionIndex = 0;
// Get options
nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(aListNode));
NS_ASSERTION(selectElement, "No select element where it should be");
nsCOMPtr<nsIDOMHTMLOptionsCollection> options;
nsresult rv = selectElement->GetOptions(getter_AddRefs(options));
if (NS_SUCCEEDED(rv)) {
nsIListControlFrame *listFrame = do_QueryFrame(frame);
if (listFrame) {
// Get what's focused in listbox by asking frame for "selected item".
// Can't use dom interface for this, because it will always return the first selected item
// when there is more than 1 item selected. We need the focused item, not
// the first selected item.
focusedOptionIndex = listFrame->GetSelectedIndex();
if (focusedOptionIndex == -1) {
nsCOMPtr<nsIDOMNode> nextOption;
while (PR_TRUE) {
++ focusedOptionIndex;
options->Item(focusedOptionIndex, getter_AddRefs(nextOption));
nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = do_QueryInterface(nextOption);
if (!optionElement) {
break;
}
PRBool disabled;
optionElement->GetDisabled(&disabled);
if (!disabled) {
break;
}
}
}
}
else // Combo boxes can only have 1 selected option, so they can use the dom interface for this
rv = selectElement->GetSelectedIndex(&focusedOptionIndex);
return mParent;
}
// Either use options and focused index, or default return null
if (NS_SUCCEEDED(rv) && options && focusedOptionIndex >= 0) { // Something is focused
nsCOMPtr<nsIDOMNode> focusedOptionNode;
options->Item(focusedOptionIndex, getter_AddRefs(focusedOptionNode));
nsIContent *focusedOption = nsnull;
if (focusedOptionNode)
CallQueryInterface(focusedOptionNode, &focusedOption);
return focusedOption;
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLSelectOptionAccessible: static methods
void
nsHTMLSelectOptionAccessible::SelectionChangedIfOption(nsIContent *aPossibleOptionNode)
{
@ -644,6 +560,7 @@ nsHTMLComboboxAccessible::
nsHTMLComboboxAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsAccessibleWrap(aContent, aShell)
{
mFlags |= eComboboxAccessible;
}
////////////////////////////////////////////////////////////////////////////////
@ -718,16 +635,12 @@ nsHTMLComboboxAccessible::NativeState()
nsIFrame *frame = GetBoundsFrame();
nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
if (comboFrame && comboFrame->IsDroppedDown()) {
if (comboFrame && comboFrame->IsDroppedDown())
state |= states::EXPANDED;
}
else {
state &= ~states::FOCUSED; // Focus is on an option
else
state |= states::COLLAPSED;
}
state |= states::HASPOPUP | states::FOCUSABLE;
state |= states::HASPOPUP;
return state;
}
@ -740,37 +653,17 @@ nsHTMLComboboxAccessible::Description(nsString& aDescription)
nsAccessible::Description(aDescription);
if (!aDescription.IsEmpty())
return;
// Use description of currently focused option
nsAccessible *option = GetFocusedOptionAccessible();
// Otherwise use description of selected option.
nsAccessible* option = SelectedOption();
if (option)
option->Description(aDescription);
}
nsAccessible *
nsHTMLComboboxAccessible::GetFocusedOptionAccessible()
{
if (IsDefunct())
return nsnull;
nsCOMPtr<nsIContent> focusedOption =
nsHTMLSelectOptionAccessible::GetFocusedOption(mContent);
if (!focusedOption) {
return nsnull;
}
return GetAccService()->GetAccessibleInWeakShell(focusedOption,
mWeakShell);
}
/**
* MSAA/ATK accessible value != HTML value, especially not in combo boxes.
* Our accessible value is the text label for of our ( first ) selected child.
* The easiest way to get this is from the first child which is the readonly textfield.
*/
NS_IMETHODIMP nsHTMLComboboxAccessible::GetValue(nsAString& aValue)
{
// Use accessible name of currently focused option.
nsAccessible *option = GetFocusedOptionAccessible();
// Use accessible name of selected option.
nsAccessible* option = SelectedOption();
return option ? option->GetName(aValue) : NS_OK;
}
@ -780,25 +673,16 @@ nsHTMLComboboxAccessible::ActionCount()
return 1;
}
/**
* Programmaticaly toggle the combo box
*/
NS_IMETHODIMP nsHTMLComboboxAccessible::DoAction(PRUint8 aIndex)
NS_IMETHODIMP
nsHTMLComboboxAccessible::DoAction(PRUint8 aIndex)
{
if (aIndex != nsHTMLComboboxAccessible::eAction_Click) {
if (aIndex != eAction_Click)
return NS_ERROR_INVALID_ARG;
}
nsIFrame *frame = GetFrame();
if (!frame) {
return NS_ERROR_FAILURE;
}
nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
if (!comboFrame) {
return NS_ERROR_FAILURE;
}
// Reverse whether it's dropped down or not
comboFrame->ShowDropDown(!comboFrame->IsDroppedDown());
if (IsDefunct())
return NS_ERROR_FAILURE;
DoCommand();
return NS_OK;
}
@ -829,6 +713,63 @@ NS_IMETHODIMP nsHTMLComboboxAccessible::GetActionName(PRUint8 aIndex, nsAString&
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLComboboxAccessible: Widgets
bool
nsHTMLComboboxAccessible::IsWidget() const
{
return true;
}
bool
nsHTMLComboboxAccessible::IsActiveWidget() const
{
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsHTMLComboboxAccessible::AreItemsOperable() const
{
nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
return comboboxFrame && comboboxFrame->IsDroppedDown();
}
nsAccessible*
nsHTMLComboboxAccessible::CurrentItem()
{
// No current item for collapsed combobox.
return SelectedOption(true);
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLComboboxAccessible: protected
nsAccessible*
nsHTMLComboboxAccessible::SelectedOption(bool aIgnoreIfCollapsed) const
{
nsIFrame* frame = GetFrame();
nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(frame);
if (comboboxFrame) {
if (aIgnoreIfCollapsed && !comboboxFrame->IsDroppedDown())
return nsnull;
frame = comboboxFrame->GetDropDown();
}
nsIListControlFrame* listControlFrame = do_QueryFrame(frame);
if (listControlFrame) {
nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
if (activeOptionNode) {
nsDocAccessible* document = GetDocAccessible();
if (document)
return document->GetAccessible(activeOptionNode);
}
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLComboboxListAccessible

View File

@ -80,6 +80,12 @@ public:
virtual bool SelectAll();
virtual bool UnselectAll();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* CurrentItem();
protected:
// nsAccessible
@ -121,10 +127,8 @@ public:
// ActionAccessible
virtual PRUint8 ActionCount();
/**
* Return focused option if any.
*/
static already_AddRefed<nsIContent> GetFocusedOption(nsIContent *aListNode);
// Widgets
virtual nsAccessible* ContainerWidget() const;
static void SelectionChangedIfOption(nsIContent *aPossibleOption);
@ -202,16 +206,20 @@ public:
// ActionAccessible
virtual PRUint8 ActionCount();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* CurrentItem();
protected:
// nsAccessible
virtual void CacheChildren();
// nsHTMLComboboxAccessible
/**
* Return focused option accessible.
* Return selected option.
*/
nsAccessible *GetFocusedOptionAccessible();
nsAccessible* SelectedOption(bool aIgnoreIfCollapsed = false) const;
private:
nsRefPtr<nsHTMLComboboxListAccessible> mListAccessible;

View File

@ -1242,7 +1242,7 @@ nsHyperTextAccessible::GetAttributesInternal(nsIPersistentProperties *aAttribute
oldValueUnused);
}
if (gLastFocusedNode == GetNode()) {
if (FocusMgr()->IsFocused(this)) {
PRInt32 lineNumber = GetCaretLineNumber();
if (lineNumber >= 1) {
nsAutoString strLineNumber;
@ -1628,13 +1628,9 @@ nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
// No caret if the focused node is not inside this DOM node and this DOM node
// is not inside of focused node.
nsINode* thisNode = GetNode();
PRBool isInsideOfFocusedNode =
nsCoreUtils::IsAncestorOf(gLastFocusedNode, thisNode);
if (!isInsideOfFocusedNode && thisNode != gLastFocusedNode &&
!nsCoreUtils::IsAncestorOf(thisNode, gLastFocusedNode))
FocusManager::FocusDisposition focusDisp =
FocusMgr()->IsInOrContainsFocus(this);
if (focusDisp == FocusManager::eNone)
return NS_OK;
// Turn the focus node and offset of the selection into caret hypretext
@ -1655,10 +1651,11 @@ nsHyperTextAccessible::GetCaretOffset(PRInt32 *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.
nsCOMPtr<nsINode> focusNode(do_QueryInterface(focusDOMNode));
if (isInsideOfFocusedNode) {
if (focusDisp == FocusManager::eContainedByFocus) {
nsINode *resultNode =
nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
nsINode* thisNode = GetNode();
if (resultNode != thisNode &&
!nsCoreUtils::IsAncestorOf(thisNode, resultNode))
return NS_OK;

View File

@ -72,4 +72,6 @@ LOCAL_INCLUDES = \
-I$(srcdir) \
-I$(srcdir)/../base \
-I$(srcdir)/../html \
-I$(srcdir)/../../../layout/generic \
-I$(srcdir)/../../../layout/xul/base/src \
$(NULL)

View File

@ -73,3 +73,21 @@ nsXULAlertAccessible::GetName(nsAString& aName)
aName.Truncate();
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// Widgets
bool
nsXULAlertAccessible::IsWidget() const
{
return true;
}
nsAccessible*
nsXULAlertAccessible::ContainerWidget() const
{
// If a part of colorpicker widget.
if (mParent && mParent->IsMenuButton())
return mParent;
return nsnull;
}

View File

@ -57,6 +57,10 @@ public:
// nsAccessible
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widgets
virtual bool IsWidget() const;
virtual nsAccessible* ContainerWidget() const;
};
#endif
#endif

View File

@ -45,6 +45,7 @@
#include "nsDocAccessible.h"
#include "nsIDOMElement.h"
#include "nsMenuPopupFrame.h"
using namespace mozilla::a11y;
@ -85,27 +86,30 @@ nsXULColorPickerTileAccessible::NativeRole()
PRUint64
nsXULColorPickerTileAccessible::NativeState()
{
// Possible states: focused, focusable, selected.
PRUint64 state = nsAccessibleWrap::NativeState();
if (!(state & states::UNAVAILABLE))
state |= states::FOCUSABLE | states::SELECTABLE;
// get focus and disable status from base class
PRUint64 states = nsAccessibleWrap::NativeState();
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::selected))
state |= states::SELECTED;
states |= states::FOCUSABLE;
// Focused?
PRBool isFocused = mContent->HasAttr(kNameSpaceID_None,
nsGkAtoms::hover);
if (isFocused)
states |= states::FOCUSED;
PRBool isSelected = mContent->HasAttr(kNameSpaceID_None,
nsGkAtoms::selected);
if (isSelected)
states |= states::SELECTED;
return states;
return state;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULColorPickerTileAccessible: Widgets
nsAccessible*
nsXULColorPickerTileAccessible::ContainerWidget() const
{
nsAccessible* parent = Parent();
if (parent) {
nsAccessible* grandParent = parent->Parent();
if (grandParent && grandParent->IsMenuButton())
return grandParent;
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULColorPickerAccessible
@ -115,6 +119,7 @@ nsXULColorPickerAccessible::
nsXULColorPickerAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsXULColorPickerTileAccessible(aContent, aShell)
{
mFlags |= eMenuButtonAccessible;
}
////////////////////////////////////////////////////////////////////////////////
@ -139,6 +144,32 @@ nsXULColorPickerAccessible::NativeRole()
return nsIAccessibleRole::ROLE_BUTTONDROPDOWNGRID;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULColorPickerAccessible: Widgets
bool
nsXULColorPickerAccessible::IsWidget() const
{
return true;
}
bool
nsXULColorPickerAccessible::IsActiveWidget() const
{
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsXULColorPickerAccessible::AreItemsOperable() const
{
nsAccessible* menuPopup = mChildren.SafeElementAt(0, nsnull);
if (menuPopup) {
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame());
return menuPopupFrame && menuPopupFrame->IsOpen();
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULColorPickerAccessible: protected nsAccessible

View File

@ -39,7 +39,6 @@
#ifndef _nsXULColorPickerAccessible_H_
#define _nsXULColorPickerAccessible_H_
// NOTE: alphabetically ordered
#include "nsAccessibleWrap.h"
/**
@ -57,6 +56,9 @@ public:
// nsAccessible
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widgets
virtual nsAccessible* ContainerWidget() const;
};
@ -72,6 +74,11 @@ public:
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
protected:
// nsAccessible

View File

@ -44,6 +44,7 @@
#include "nsAccessibilityService.h"
#include "nsCoreUtils.h"
#include "nsIAutoCompleteInput.h"
#include "nsIDOMXULMenuListElement.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
@ -57,13 +58,17 @@ nsXULComboboxAccessible::
nsXULComboboxAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsAccessibleWrap(aContent, aShell)
{
if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::autocomplete, eIgnoreCase))
mFlags |= eAutoCompleteAccessible;
else
mFlags |= eComboboxAccessible;
}
PRUint32
nsXULComboboxAccessible::NativeRole()
{
if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::autocomplete, eIgnoreCase))
if (IsAutoComplete())
return nsIAccessibleRole::ROLE_AUTOCOMPLETE;
return nsIAccessibleRole::ROLE_COMBOBOX;
}
@ -206,3 +211,48 @@ nsXULComboboxAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// Widgets
bool
nsXULComboboxAccessible::IsActiveWidget() const
{
if (IsAutoComplete() ||
mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
nsGkAtoms::_true, eIgnoreCase)) {
PRInt32 childCount = mChildren.Length();
for (PRInt32 idx = 0; idx < childCount; idx++) {
nsAccessible* child = mChildren[idx];
if (child->Role() == nsIAccessibleRole::ROLE_ENTRY)
return FocusMgr()->HasDOMFocus(child->GetContent());
}
return false;
}
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsXULComboboxAccessible::AreItemsOperable() const
{
if (IsAutoComplete()) {
nsCOMPtr<nsIAutoCompleteInput> autoCompleteInputElm =
do_QueryInterface(mContent);
if (autoCompleteInputElm) {
PRBool isOpen = PR_FALSE;
autoCompleteInputElm->GetPopupOpen(&isOpen);
return isOpen;
}
return false;
}
nsCOMPtr<nsIDOMXULMenuListElement> menuListElm = do_QueryInterface(mContent);
if (menuListElm) {
PRBool isOpen = PR_FALSE;
menuListElm->GetOpen(&isOpen);
return isOpen;
}
return false;
}

View File

@ -40,7 +40,6 @@
#ifndef __nsXULComboboxAccessible_h__
#define __nsXULComboboxAccessible_h__
#include "nsCOMPtr.h"
#include "nsXULMenuAccessible.h"
/**
@ -66,6 +65,10 @@ public:
// ActionAccessible
virtual PRUint8 ActionCount();
// Widgets
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
};
#endif

View File

@ -61,6 +61,7 @@
#include "nsIFrame.h"
#include "nsINameSpaceManager.h"
#include "nsITextControlFrame.h"
#include "nsMenuPopupFrame.h"
using namespace mozilla::a11y;
@ -72,6 +73,8 @@ nsXULButtonAccessible::
nsXULButtonAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsAccessibleWrap(aContent, aShell)
{
if (ContainsMenu())
mFlags |= eMenuButtonAccessible;
}
////////////////////////////////////////////////////////////////////////////////
@ -164,6 +167,42 @@ nsXULButtonAccessible::NativeState()
return state;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULButtonAccessible: Widgets
bool
nsXULButtonAccessible::IsWidget() const
{
return true;
}
bool
nsXULButtonAccessible::IsActiveWidget() const
{
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsXULButtonAccessible::AreItemsOperable() const
{
if (IsMenuButton()) {
nsAccessible* menuPopup = mChildren.SafeElementAt(0, nsnull);
if (menuPopup) {
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame());
return menuPopupFrame->IsOpen();
}
}
return false; // no items
}
nsAccessible*
nsXULButtonAccessible::ContainerWidget() const
{
if (IsMenuButton() && mParent && mParent->IsAutoComplete())
return mParent;
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULButtonAccessible: nsAccessible protected
@ -464,19 +503,19 @@ nsXULRadioButtonAccessible::
{
}
/** We are Focusable and can be Checked and focused */
PRUint64
nsXULRadioButtonAccessible::NativeState()
{
PRUint64 state = nsFormControlAccessible::NativeState();
state |= states::CHECKABLE;
PRBool selected = PR_FALSE; // Radio buttons can be selected
if (!(state & states::UNAVAILABLE))
state |= states::FOCUSABLE;
nsCOMPtr<nsIDOMXULSelectControlItemElement> radioButton =
do_QueryInterface(mContent);
if (radioButton) {
PRBool selected = PR_FALSE; // Radio buttons can be selected
radioButton->GetSelected(&selected);
if (selected) {
state |= states::CHECKED;
@ -494,6 +533,15 @@ nsXULRadioButtonAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet,
aSetSize);
}
////////////////////////////////////////////////////////////////////////////////
// nsXULRadioButtonAccessible: Widgets
nsAccessible*
nsXULRadioButtonAccessible::ContainerWidget() const
{
return mParent;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULRadioGroupAccessible
@ -529,7 +577,28 @@ nsXULRadioGroupAccessible::NativeState()
return nsAccessible::NativeState() & ~(states::FOCUSABLE | states::FOCUSED);
}
////////////////////////////////////////////////////////////////////////////////
// nsXULRadioGroupAccessible: Widgets
bool
nsXULRadioGroupAccessible::IsWidget() const
{
return true;
}
bool
nsXULRadioGroupAccessible::IsActiveWidget() const
{
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsXULRadioGroupAccessible::AreItemsOperable() const
{
return true;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULStatusBarAccessible
////////////////////////////////////////////////////////////////////////////////
@ -719,9 +788,6 @@ nsXULTextFieldAccessible::NativeState()
state |= tempAccessible->NativeState();
if (gLastFocusedNode == mContent)
state |= states::FOCUSED;
nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
if (menuList) {
// <xul:menulist droppable="false">

View File

@ -77,6 +77,12 @@ public:
// ActionAccessible
virtual PRUint8 ActionCount();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* ContainerWidget() const;
protected:
// nsAccessible
@ -159,6 +165,9 @@ public:
virtual void GetPositionAndSizeInternal(PRInt32 *aPosInSet,
PRInt32 *aSetSize);
virtual PRUint64 NativeState();
// Widgets
virtual nsAccessible* ContainerWidget() const;
};
/**
@ -172,6 +181,11 @@ public:
// nsAccessible
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
};
/**

View File

@ -44,11 +44,14 @@
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsIDOMXULPopupElement.h"
#include "nsIDOMXULMultSelectCntrlEl.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
#include "nsIDOMNodeList.h"
#include "nsComponentManagerUtils.h"
#include "nsIAutoCompleteInput.h"
#include "nsIAutoCompletePopup.h"
#include "nsIDOMXULMenuListElement.h"
#include "nsIDOMXULMultSelectCntrlEl.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMXULPopupElement.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
using namespace mozilla::a11y;
@ -131,6 +134,13 @@ nsXULListboxAccessible::
nsXULListboxAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsXULSelectableAccessible(aContent, aShell)
{
nsIContent* parentContent = mContent->GetParent();
if (parentContent) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(parentContent);
if (autoCompletePopupElm)
mFlags |= eAutoCompletePopupAccessible;
}
}
NS_IMPL_ADDREF_INHERITED(nsXULListboxAccessible, nsXULSelectableAccessible)
@ -825,6 +835,72 @@ nsXULListboxAccessible::IsProbablyForLayout(PRBool *aIsProbablyForLayout)
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULListboxAccessible: Widgets
bool
nsXULListboxAccessible::IsWidget() const
{
return true;
}
bool
nsXULListboxAccessible::IsActiveWidget() const
{
if (IsAutoCompletePopup()) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(mContent->GetParent());
if (autoCompletePopupElm) {
PRBool isOpen = PR_FALSE;
autoCompletePopupElm->GetPopupOpen(&isOpen);
return isOpen;
}
}
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsXULListboxAccessible::AreItemsOperable() const
{
if (IsAutoCompletePopup()) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(mContent->GetParent());
if (autoCompletePopupElm) {
PRBool isOpen = PR_FALSE;
autoCompletePopupElm->GetPopupOpen(&isOpen);
return isOpen;
}
}
return true;
}
nsAccessible*
nsXULListboxAccessible::ContainerWidget() const
{
if (IsAutoCompletePopup()) {
// This works for XUL autocompletes. It doesn't work for HTML forms
// autocomplete because of potential crossprocess calls (when autocomplete
// lives in content process while popup lives in chrome process). If that's
// a problem then rethink Widgets interface.
nsCOMPtr<nsIDOMXULMenuListElement> menuListElm =
do_QueryInterface(mContent->GetParent());
if (menuListElm) {
nsCOMPtr<nsIDOMNode> inputElm;
menuListElm->GetInputField(getter_AddRefs(inputElm));
if (inputElm) {
nsCOMPtr<nsINode> inputNode = do_QueryInterface(inputElm);
if (inputNode) {
nsAccessible* input = GetAccService()->GetAccessible(inputNode);
return input ? input->ContainerWidget() : nsnull;
}
}
}
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULListitemAccessible
////////////////////////////////////////////////////////////////////////////////
@ -930,9 +1006,8 @@ nsXULListitemAccessible::NativeState()
if (isSelected)
states |= states::SELECTED;
if (gLastFocusedNode == mContent)
if (FocusMgr()->IsFocused(this))
states |= states::FOCUSED;
}
return states;
@ -969,6 +1044,15 @@ nsXULListitemAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet,
aSetSize);
}
////////////////////////////////////////////////////////////////////////////////
// nsXULListitemAccessible: Widgets
nsAccessible*
nsXULListitemAccessible::ContainerWidget() const
{
return Parent();
}
////////////////////////////////////////////////////////////////////////////////
// nsXULListCellAccessible

View File

@ -105,6 +105,13 @@ public:
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* ContainerWidget() const;
protected:
PRBool IsMulticolumn();
};
@ -135,6 +142,9 @@ public:
PRInt32 *aSetSize);
virtual PRBool GetAllowsAnonChildAccessibles();
// Widgets
virtual nsAccessible* ContainerWidget() const;
protected:
/**
* Return listbox accessible for the listitem.

View File

@ -40,6 +40,7 @@
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsDocAccessible.h"
#include "nsXULFormControlAccessible.h"
#include "States.h"
@ -54,6 +55,8 @@
#include "nsIPresShell.h"
#include "nsIContent.h"
#include "nsGUIEvent.h"
#include "nsMenuBarFrame.h"
#include "nsMenuPopupFrame.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
@ -269,6 +272,35 @@ nsXULSelectableAccessible::SelectAll()
return false;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULSelectableAccessible: Widgets
nsAccessible*
nsXULSelectableAccessible::CurrentItem()
{
if (!mSelectControl)
return nsnull;
nsCOMPtr<nsIDOMXULSelectControlItemElement> currentItemElm;
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
do_QueryInterface(mSelectControl);
if (multiSelectControl)
multiSelectControl->GetCurrentItem(getter_AddRefs(currentItemElm));
else
mSelectControl->GetSelectedItem(getter_AddRefs(currentItemElm));
nsCOMPtr<nsINode> DOMNode;
if (currentItemElm)
DOMNode = do_QueryInterface(currentItemElm);
if (DOMNode) {
nsDocAccessible* document = GetDocAccessible();
if (document)
return document->GetAccessible(DOMNode);
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULMenuitemAccessible
@ -285,10 +317,6 @@ nsXULMenuitemAccessible::NativeState()
{
PRUint64 state = nsAccessible::NativeState();
// Focused?
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive))
state |= states::FOCUSED;
// Has Popup?
if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) {
state |= states::HASPOPUP;
@ -362,7 +390,10 @@ nsXULMenuitemAccessible::NativeState()
return state;
}
}
state |= (states::FOCUSABLE | states::SELECTABLE);
if (FocusMgr()->IsFocused(this))
state |= states::FOCUSED;
return state;
}
@ -563,6 +594,56 @@ nsXULMenuitemAccessible::ActionCount()
return 1;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULMenuitemAccessible: Widgets
bool
nsXULMenuitemAccessible::IsActiveWidget() const
{
// Parent menu item is a widget, it's active when its popup is open.
nsIContent* menuPopupContent = mContent->GetFirstChild();
if (menuPopupContent) {
nsMenuPopupFrame* menuPopupFrame =
do_QueryFrame(menuPopupContent->GetPrimaryFrame());
return menuPopupFrame && menuPopupFrame->IsOpen();
}
return false;
}
bool
nsXULMenuitemAccessible::AreItemsOperable() const
{
// Parent menu item is a widget, its items are operable when its popup is open.
nsIContent* menuPopupContent = mContent->GetFirstChild();
if (menuPopupContent) {
nsMenuPopupFrame* menuPopupFrame =
do_QueryFrame(menuPopupContent->GetPrimaryFrame());
return menuPopupFrame && menuPopupFrame->IsOpen();
}
return false;
}
nsAccessible*
nsXULMenuitemAccessible::ContainerWidget() const
{
nsMenuFrame* menuFrame = do_QueryFrame(GetFrame());
if (menuFrame) {
nsMenuParent* menuParent = menuFrame->GetMenuParent();
if (menuParent) {
if (menuParent->IsMenuBar()) // menubar menu
return mParent;
// a menupoup or parent menu item
if (menuParent->IsMenu())
return mParent;
// otherwise it's different kind of popups (like panel or tooltip), it
// shouldn't be a real case.
}
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULMenuSeparatorAccessible
@ -617,7 +698,11 @@ nsXULMenuSeparatorAccessible::ActionCount()
nsXULMenupopupAccessible::
nsXULMenupopupAccessible(nsIContent *aContent, nsIWeakReference *aShell) :
nsXULSelectableAccessible(aContent, aShell)
{
{
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
if (menuPopupFrame && menuPopupFrame->IsMenu())
mFlags |= eMenuPopupAccessible;
// May be the anonymous <menupopup> inside <menulist> (a combobox)
mSelectControl = do_QueryInterface(mContent->GetParent());
}
@ -689,6 +774,65 @@ nsXULMenupopupAccessible::NativeRole()
return nsIAccessibleRole::ROLE_MENUPOPUP;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULMenupopupAccessible: Widgets
bool
nsXULMenupopupAccessible::IsWidget() const
{
return true;
}
bool
nsXULMenupopupAccessible::IsActiveWidget() const
{
// If menupopup is a widget (the case of context menus) then active when open.
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
return menuPopupFrame && menuPopupFrame->IsOpen();
}
bool
nsXULMenupopupAccessible::AreItemsOperable() const
{
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
return menuPopupFrame && menuPopupFrame->IsOpen();
}
nsAccessible*
nsXULMenupopupAccessible::ContainerWidget() const
{
nsDocAccessible* document = GetDocAccessible();
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
while (menuPopupFrame) {
nsAccessible* menuPopup =
document->GetAccessible(menuPopupFrame->GetContent());
if (!menuPopup) // shouldn't be a real case
return nsnull;
nsMenuFrame* menuFrame = menuPopupFrame->GetParentMenu();
if (!menuFrame) // context menu or popups
return nsnull;
nsMenuParent* menuParent = menuFrame->GetMenuParent();
if (!menuParent) // menulist or menubutton
return menuPopup->Parent();
if (menuParent->IsMenuBar()) { // menubar menu
nsMenuBarFrame* menuBarFrame = static_cast<nsMenuBarFrame*>(menuParent);
return document->GetAccessible(menuBarFrame->GetContent());
}
// different kind of popups like panel or tooltip
if (!menuParent->IsMenu())
return nsnull;
menuPopupFrame = static_cast<nsMenuPopupFrame*>(menuParent);
}
NS_NOTREACHED("Shouldn't be a real case.");
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULMenubarAccessible
@ -724,3 +868,32 @@ nsXULMenubarAccessible::NativeRole()
return nsIAccessibleRole::ROLE_MENUBAR;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULMenubarAccessible: Widgets
bool
nsXULMenubarAccessible::IsActiveWidget() const
{
nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame());
return menuBarFrame && menuBarFrame->IsActive();
}
bool
nsXULMenubarAccessible::AreItemsOperable() const
{
return true;
}
nsAccessible*
nsXULMenubarAccessible::CurrentItem()
{
nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame());
if (menuBarFrame) {
nsMenuFrame* menuFrame = menuBarFrame->GetCurrentMenuItem();
if (menuFrame) {
nsIContent* menuItemNode = menuFrame->GetContent();
return GetAccService()->GetAccessible(menuItemNode);
}
}
return nsnull;
}

View File

@ -65,6 +65,9 @@ public:
virtual bool SelectAll();
virtual bool UnselectAll();
// Widgets
virtual nsAccessible* CurrentItem();
protected:
// nsIDOMXULMultiSelectControlElement inherits from this, so we'll always have
// one of these if the widget is valid and not defunct
@ -100,6 +103,11 @@ public:
virtual PRUint8 ActionCount();
virtual KeyBinding AccessKey() const;
virtual KeyBinding KeyboardShortcut() const;
// Widgets
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* ContainerWidget() const;
};
/**
@ -136,6 +144,13 @@ public:
virtual nsresult GetNameInternal(nsAString& aName);
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* ContainerWidget() const;
};
/**
@ -150,6 +165,11 @@ public:
virtual nsresult GetNameInternal(nsAString& aName);
virtual PRUint32 NativeRole();
virtual PRUint64 NativeState();
// Widget
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* CurrentItem();
};
#endif

View File

@ -38,6 +38,7 @@
#include "nsXULSliderAccessible.h"
#include "nsAccessibilityService.h"
#include "States.h"
#include "nsIDOMDocument.h"
@ -82,7 +83,7 @@ nsXULSliderAccessible::NativeState()
if (frame && frame->IsFocusable())
states |= states::FOCUSABLE;
if (gLastFocusedNode == mContent)
if (FocusMgr()->IsFocused(this))
states |= states::FOCUSED;
return states;

View File

@ -46,13 +46,16 @@
#include "Relation.h"
#include "States.h"
#include "nsComponentManagerUtils.h"
#include "nsIAccessibleRelation.h"
#include "nsIAutoCompleteInput.h"
#include "nsIAutoCompletePopup.h"
#include "nsIDOMXULElement.h"
#include "nsIDOMXULMenuListElement.h"
#include "nsIDOMXULMultSelectCntrlEl.h"
#include "nsIDOMXULTreeElement.h"
#include "nsITreeSelection.h"
#include "nsIMutableArray.h"
#include "nsComponentManagerUtils.h"
using namespace mozilla::a11y;
@ -70,6 +73,14 @@ nsXULTreeAccessible::
NS_ASSERTION(mTree && mTreeView, "Can't get mTree or mTreeView!\n");
nsIContent* parentContent = mContent->GetParent();
if (parentContent) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(parentContent);
if (autoCompletePopupElm)
mFlags |= eAutoCompletePopupAccessible;
}
mAccessibleCache.Init(kDefaultTreeCacheSize);
}
@ -107,9 +118,6 @@ nsXULTreeAccessible::NativeState()
// readonly state
state |= states::READONLY;
// remove focusable and focused states since tree items are focusable for AT
state &= ~(states::FOCUSABLE | states::FOCUSED);
// multiselectable state.
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
@ -201,27 +209,6 @@ nsXULTreeAccessible::NativeRole()
static_cast<PRUint32>(nsIAccessibleRole::ROLE_LIST);
}
////////////////////////////////////////////////////////////////////////////////
// nsXULTreeAccessible: nsIAccessible implementation
nsAccessible*
nsXULTreeAccessible::FocusedChild()
{
if (gLastFocusedNode != mContent)
return nsnull;
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
do_QueryInterface(mContent);
if (multiSelect) {
PRInt32 row = -1;
multiSelect->GetCurrentIndex(&row);
if (row >= 0)
return GetTreeItemAccessible(row);
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULTreeAccessible: nsAccessible implementation (DON'T put methods here)
@ -277,6 +264,21 @@ nsXULTreeAccessible::IsSelect()
return true;
}
nsAccessible*
nsXULTreeAccessible::CurrentItem()
{
nsCOMPtr<nsITreeSelection> selection;
mTreeView->GetSelection(getter_AddRefs(selection));
if (selection) {
PRInt32 currentIndex = -1;
selection->GetCurrentIndex(&currentIndex);
if (currentIndex >= 0)
return GetTreeItemAccessible(currentIndex);
}
return nsnull;
}
already_AddRefed<nsIArray>
nsXULTreeAccessible::SelectedItems()
{
@ -452,6 +454,72 @@ nsXULTreeAccessible::GetChildCount()
return childCount;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULTreeAccessible: Widgets
bool
nsXULTreeAccessible::IsWidget() const
{
return true;
}
bool
nsXULTreeAccessible::IsActiveWidget() const
{
if (IsAutoCompletePopup()) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(mContent->GetParent());
if (autoCompletePopupElm) {
PRBool isOpen = PR_FALSE;
autoCompletePopupElm->GetPopupOpen(&isOpen);
return isOpen;
}
}
return FocusMgr()->HasDOMFocus(mContent);
}
bool
nsXULTreeAccessible::AreItemsOperable() const
{
if (IsAutoCompletePopup()) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
do_QueryInterface(mContent->GetParent());
if (autoCompletePopupElm) {
PRBool isOpen = PR_FALSE;
autoCompletePopupElm->GetPopupOpen(&isOpen);
return isOpen;
}
}
return true;
}
nsAccessible*
nsXULTreeAccessible::ContainerWidget() const
{
if (IsAutoCompletePopup()) {
// This works for XUL autocompletes. It doesn't work for HTML forms
// autocomplete because of potential crossprocess calls (when autocomplete
// lives in content process while popup lives in chrome process). If that's
// a problem then rethink Widgets interface.
nsCOMPtr<nsIDOMXULMenuListElement> menuListElm =
do_QueryInterface(mContent->GetParent());
if (menuListElm) {
nsCOMPtr<nsIDOMNode> inputElm;
menuListElm->GetInputField(getter_AddRefs(inputElm));
if (inputElm) {
nsCOMPtr<nsINode> inputNode = do_QueryInterface(inputElm);
if (inputNode) {
nsAccessible* input = GetAccService()->GetAccessible(inputNode);
return input ? input->ContainerWidget() : nsnull;
}
}
}
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULTreeAccessible: public implementation
@ -662,20 +730,7 @@ NS_IMPL_RELEASE_INHERITED(nsXULTreeItemAccessibleBase, nsAccessible)
nsAccessible*
nsXULTreeItemAccessibleBase::FocusedChild()
{
if (gLastFocusedNode != mContent)
return nsnull;
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
do_QueryInterface(mContent);
if (multiSelect) {
PRInt32 row = -1;
multiSelect->GetCurrentIndex(&row);
if (row == mRow)
return this;
}
return nsnull;
return FocusMgr()->FocusedAccessible() == this ? this : nsnull;
}
NS_IMETHODIMP
@ -944,15 +999,8 @@ nsXULTreeItemAccessibleBase::NativeState()
}
// focused state
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
do_QueryInterface(mContent);
if (multiSelect) {
PRInt32 currentIndex;
multiSelect->GetCurrentIndex(&currentIndex);
if (currentIndex == mRow) {
state |= states::FOCUSED;
}
}
if (FocusMgr()->IsFocused(this))
state |= states::FOCUSED;
// invisible state
PRInt32 firstVisibleRow, lastVisibleRow;
@ -970,6 +1018,15 @@ nsXULTreeItemAccessibleBase::IndexInParent() const
return mParent ? mParent->ContentChildCount() + mRow : -1;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULTreeItemAccessibleBase: Widgets
nsAccessible*
nsXULTreeItemAccessibleBase::ContainerWidget() const
{
return mParent;
}
////////////////////////////////////////////////////////////////////////////////
// nsXULTreeItemAccessibleBase: nsAccessible protected methods

View File

@ -86,7 +86,6 @@ public:
virtual PRUint64 NativeState();
virtual nsAccessible* ChildAtPoint(PRInt32 aX, PRInt32 aY,
EWhichChildAtPoint aWhichChild);
virtual nsAccessible* FocusedChild();
virtual nsAccessible* GetChildAt(PRUint32 aIndex);
virtual PRInt32 GetChildCount();
@ -102,6 +101,14 @@ public:
virtual bool SelectAll();
virtual bool UnselectAll();
// Widgets
virtual bool IsWidget() const;
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
virtual nsAccessible* CurrentItem();
virtual nsAccessible* ContainerWidget() const;
// nsXULTreeAccessible
NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULTREEACCESSIBLE_IMPL_CID)
@ -209,6 +216,9 @@ public:
// ActionAccessible
virtual PRUint8 ActionCount();
// Widgets
virtual nsAccessible* ContainerWidget() const;
// nsXULTreeItemAccessibleBase
NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULTREEITEMBASEACCESSIBLE_IMPL_CID)

View File

@ -72,19 +72,18 @@ _TEST_FILES =\
longdesc_src.html \
actions.js \
attributes.js \
autocomplete.js \
common.js \
events.js \
grid.js \
layout.js \
name.js \
nsIAccessible_selects.js \
relations.js \
role.js \
selectable.js \
states.js \
table.js \
value.js \
test_aria_activedescendant.html \
test_aria_role_article.html \
test_aria_role_equation.html \
test_aria_roles.html \
@ -98,7 +97,6 @@ _TEST_FILES =\
test_elm_listbox.xul \
test_elm_nsApplicationAcc.html \
test_elm_plugin.html \
test_nsIAccessible_selects.html \
test_nsIAccessibleDocument.html \
test_nsIAccessibleImage.html \
test_nsIAccessNode_utils.html \

View File

@ -55,6 +55,7 @@ _TEST_FILES =\
test_keys.html \
test_link.html \
test_media.html \
test_select.html \
test_tree.xul \
test_treegrid.xul \
$(NULL)

View File

@ -0,0 +1,103 @@
<html>
<head>
<title>nsIAccessible actions testing for HTML select</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
src="../actions.js"></script>
<script type="application/javascript">
//gA11yEventDumpToConsole = true; // debugging
function doTest()
{
var actionsArray = [
{
ID: "lb_apple",
actionIndex: 0,
actionName: "select",
events: CLICK_EVENTS,
eventSeq: [
new focusChecker("lb_apple")
]
},
{
ID: "combobox",
actionIndex: 0,
actionName: "open",
events: CLICK_EVENTS,
eventSeq: [
new focusChecker("cb_orange")
]
},
{
ID: "cb_apple",
actionIndex: 0,
actionName: "select",
events: CLICK_EVENTS,
eventSeq: [
new focusChecker("combobox")
]
},
{
ID: "combobox",
actionIndex: 0,
actionName: "open",
events: CLICK_EVENTS,
eventSeq: [
new focusChecker("cb_apple")
]
},
{
ID: "combobox",
actionIndex: 0,
actionName: "close",
events: CLICK_EVENTS,
eventSeq: [
new focusChecker("combobox")
]
}
];
testActions(actionsArray);
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<select id="listbox" size="2">
<option id="lb_orange">orange</option>
<option id="lb_apple">apple</option>
</select>
<select id="combobox">
<option id="cb_orange">orange</option>
<option id="cb_apple">apple</option>
</select>
</body>
</html>

View File

@ -26,14 +26,10 @@
////////////////////////////////////////////////////////////////////////////
// Accessible tree testers
function focusChecker(aAcc, aStates)
function stateFocusChecker(aAcc, aStates)
{
this.type = EVENT_FOCUS;
this.target = aAcc;
this.getID = function focusChecker_getID()
{
return "focus handling";
}
this.__proto__ = new focusChecker(aAcc);
this.check = function focusChecker_check(aEvent)
{
var states = aStates ? aStates : 0;
@ -45,6 +41,7 @@
// Test
// gA11yEventDumpID = "debug";
//gA11yEventDumpToConsole = true; // debug
function doTestActions()
{
@ -64,7 +61,7 @@
events: CLICK_EVENTS,
targetID: treeBodyNode,
eventSeq: [
new focusChecker(expandedTreeItem, STATE_EXPANDED)
new stateFocusChecker(expandedTreeItem, STATE_EXPANDED)
]
},
{

View File

@ -70,6 +70,8 @@
}
}
//gA11yEventDumpToConsole = true; // debug stuff
var gQueue = null;
function doTest()
{

View File

@ -0,0 +1,216 @@
const nsISupports = Components.interfaces.nsISupports;
const nsIAutoCompleteResult = Components.interfaces.nsIAutoCompleteResult;
const nsIAutoCompleteSearch = Components.interfaces.nsIAutoCompleteSearch;
const nsIFactory = Components.interfaces.nsIFactory;
const nsIUUIDGenerator = Components.interfaces.nsIUUIDGenerator;
const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
var gDefaultAutoCompleteSearch = null;
/**
* Register 'test-a11y-search' AutoCompleteSearch.
*
* @param aValues [in] set of possible results values
* @param aComments [in] set of possible results descriptions
*/
function initAutoComplete(aValues, aComments)
{
var allResults = new ResultsHeap(aValues, aComments);
gDefaultAutoCompleteSearch =
new AutoCompleteSearch("test-a11y-search", allResults);
registerAutoCompleteSearch(gDefaultAutoCompleteSearch,
"Accessibility Test AutoCompleteSearch");
}
/**
* Unregister 'test-a11y-search' AutoCompleteSearch.
*/
function shutdownAutoComplete()
{
unregisterAutoCompleteSearch(gDefaultAutoCompleteSearch);
gDefaultAutoCompleteSearch.cid = null;
gDefaultAutoCompleteSearch = null;
}
/**
* Register the given AutoCompleteSearch.
*
* @param aSearch [in] AutoCompleteSearch object
* @param aDescription [in] description of the search object
*/
function registerAutoCompleteSearch(aSearch, aDescription)
{
var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name;
var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].
getService(nsIUUIDGenerator);
var cid = uuidGenerator.generateUUID();
var componentManager = Components.manager.QueryInterface(nsIComponentRegistrar);
componentManager.registerFactory(cid, aDescription, name, aSearch);
// Keep the id on the object so we can unregister later.
aSearch.cid = cid;
}
/**
* Unregister the given AutoCompleteSearch.
*/
function unregisterAutoCompleteSearch(aSearch)
{
var componentManager = Components.manager.QueryInterface(nsIComponentRegistrar);
componentManager.unregisterFactory(aSearch.cid, aSearch);
}
/**
* A container to keep all possible results of autocomplete search.
*/
function ResultsHeap(aValues, aComments)
{
this.values = aValues;
this.comments = aComments;
}
ResultsHeap.prototype =
{
constructor: ResultsHeap,
/**
* Return AutoCompleteResult for the given search string.
*/
getAutoCompleteResultFor: function(aSearchString)
{
var values = [], comments = [];
for (var idx = 0; idx < this.values.length; idx++) {
if (this.values[idx].indexOf(aSearchString) != -1) {
values.push(this.values[idx]);
comments.push(this.comments[idx]);
}
}
return new AutoCompleteResult(values, comments);
}
}
/**
* nsIAutoCompleteSearch implementation.
*
* @param aName [in] the name of autocomplete search
* @param aAllResults [in] ResultsHeap object
*/
function AutoCompleteSearch(aName, aAllResults)
{
this.name = aName;
this.allResults = aAllResults;
}
AutoCompleteSearch.prototype =
{
constructor: AutoCompleteSearch,
// nsIAutoCompleteSearch implementation
startSearch: function(aSearchString, aSearchParam, aPreviousResult,
aListener)
{
var result = this.allResults.getAutoCompleteResultFor(aSearchString);
aListener.onSearchResult(this, result);
},
stopSearch: function() {},
// nsISupports implementation
QueryInterface: function(iid)
{
if (iid.equals(nsISupports) ||
iid.equals(nsIFactory) ||
iid.equals(nsIAutoCompleteSearch))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
// nsIFactory implementation
createInstance: function(outer, iid)
{
return this.QueryInterface(iid);
},
// Search name. Used by AutoCompleteController.
name: null,
// Results heap.
allResults: null
}
/**
* nsIAutoCompleteResult implementation.
*/
function AutoCompleteResult(aValues, aComments)
{
this.values = aValues;
this.comments = aComments;
if (this.values.length > 0)
this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
else
this.searchResult = nsIAutoCompleteResult.NOMATCH;
}
AutoCompleteResult.prototype =
{
constructor: AutoCompleteResult,
searchString: "",
searchResult: null,
defaultIndex: 0,
get matchCount()
{
return this.values.length;
},
getValueAt: function(aIndex)
{
return this.values[aIndex];
},
getLabelAt: function(aIndex)
{
return this.getValueAt(aIndex);
},
getCommentAt: function(aIndex)
{
return this.comments[aIndex];
},
getStyleAt: function(aIndex)
{
return null;
},
getImageAt: function(aIndex)
{
return "";
},
removeValueAt: function (aRowIndex, aRemoveFromDb) {},
// nsISupports implementation
QueryInterface: function(iid) {
if (iid.equals(nsISupports) ||
iid.equals(nsIAutoCompleteResult))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
// Data
values: null,
comments: null
}

View File

@ -13,16 +13,14 @@ const nsIAccessibleTextChangeEvent =
const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates;
const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole;
const nsIAccessibleTypes = Components.interfaces.nsIAccessibleTypes;
const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType;
const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType;
const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation;
const nsIAccessNode = Components.interfaces.nsIAccessNode;
const nsIAccessible = Components.interfaces.nsIAccessible;
const nsIAccessibleCoordinateType =
Components.interfaces.nsIAccessibleCoordinateType;
const nsIAccessibleDocument = Components.interfaces.nsIAccessibleDocument;
const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication;
@ -62,6 +60,8 @@ const WIN = (navigator.platform.indexOf("Win") != -1)? true : false;
const STATE_BUSY = nsIAccessibleStates.STATE_BUSY;
const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE;
const kEmbedChar = String.fromCharCode(0xfffc);
const kDiscBulletText = String.fromCharCode(0x2022) + " ";
@ -69,9 +69,10 @@ const kCircleBulletText = String.fromCharCode(0x25e6) + " ";
const kSquareBulletText = String.fromCharCode(0x25aa) + " ";
/**
* nsIAccessibleRetrieval, initialized when test is loaded.
* nsIAccessibleRetrieval service.
*/
var gAccRetrieval = null;
var gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
getService(nsIAccessibleRetrieval);
/**
* Invokes the given function when document is loaded and focused. Preferable
@ -80,14 +81,15 @@ var gAccRetrieval = null;
*
* @param aFunc the function to invoke
*/
function addA11yLoadEvent(aFunc)
function addA11yLoadEvent(aFunc, aWindow)
{
function waitForDocLoad()
{
window.setTimeout(
function()
{
var accDoc = getAccessible(document);
var targetDocument = aWindow ? aWindow.document : document;
var accDoc = getAccessible(targetDocument);
var state = {};
accDoc.getState(state, {});
if (state.value & STATE_BUSY)
@ -99,7 +101,7 @@ function addA11yLoadEvent(aFunc)
);
}
SimpleTest.waitForFocus(waitForDocLoad);
SimpleTest.waitForFocus(waitForDocLoad, aWindow);
}
////////////////////////////////////////////////////////////////////////////////
@ -589,21 +591,15 @@ function prettyName(aIdentifier)
msg += "defunct";
}
if (acc) {
var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
var match = exp.exec(acc.valueOf());
if (match)
msg += ", address: " + match[1];
else
msg += ", address: " + acc.valueOf();
}
if (acc)
msg += ", address: " + getObjAddress(acc);
msg += "]";
return msg;
}
if (aIdentifier instanceof nsIDOMNode)
return getNodePrettyName(aIdentifier);
return "[ " + getNodePrettyName(aIdentifier) + " ]";
return " '" + aIdentifier + "' ";
}
@ -615,27 +611,30 @@ function prettyName(aIdentifier)
////////////////////////////////////////////////////////////////////////////////
// Accessible general
function initialize()
{
gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
getService(nsIAccessibleRetrieval);
}
addLoadEvent(initialize);
function getNodePrettyName(aNode)
{
try {
if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE)
return " 'document node' ";
var tag = "";
if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) {
tag = "document";
} else {
tag = aNode.localName;
if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id"))
tag += "@id=\"" + aNode.getAttribute("id") + "\"";
}
var name = " '" + aNode.localName;
if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id"))
name += "@id='" + aNode.getAttribute("id") + "'";
name += " node' "
return name;
return "'" + tag + " node', address: " + getObjAddress(aNode);
} catch (e) {
return "' no node info '";
}
}
function getObjAddress(aObj)
{
var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
var match = exp.exec(aObj.valueOf());
if (match)
return match[1];
return aObj.valueOf();
}

View File

@ -168,6 +168,9 @@ const DO_NOT_FINISH_TEST = 1;
* // * DOM event phase (false - bubbling). *
* // phase getter: function() {},
* //
* // * Callback, called to match handled event. *
* // match : function() {},
* //
* // * Callback, called when event is handled
* // check: function(aEvent) {},
* //
@ -175,7 +178,13 @@ const DO_NOT_FINISH_TEST = 1;
* // getID: function() {},
* //
* // * Event that don't have predefined order relative other events. *
* // async getter: function() {}
* // async getter: function() {},
* //
* // * Event that is not expected. *
* // unexpected getter: function() {},
* //
* // * No other event of the same type is not allowed. *
* // unique getter: function() {}
* // };
* eventSeq getter() {},
*
@ -316,7 +325,7 @@ function eventQueue(aEventType)
}
// Check in timeout invoker didn't fire registered events.
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 100,
this);
}
@ -344,11 +353,11 @@ function eventQueue(aEventType)
var idx = 0;
for (; idx < this.mEventSeq.length; idx++) {
if (this.isEventExpected(idx) && (invoker.wasCaught[idx] == true) &&
this.isAlreadyCaught(idx, aEvent)) {
this.isSameEvent(idx, aEvent)) {
var msg = "Doubled event { event type: " +
this.getEventTypeAsString(idx) + ", target: " +
prettyName(this.getEventTarget(idx)) + "} in test with ID = '" +
this.getEventTargetDescr(idx) + "} in test with ID = '" +
this.getEventID(idx) + "'.";
ok(false, msg);
}
@ -445,16 +454,49 @@ function eventQueue(aEventType)
this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
{
// Create unique event sequence concatenating expected and unexpected
// Create unified event sequence concatenating expected and unexpected
// events.
this.mEventSeq = ("eventSeq" in aInvoker) ?
aInvoker.eventSeq :
[ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
this.mEventSeq[idx].unexpected = false;
var len = this.mEventSeq.length;
for (var idx = 0; idx < len; idx++) {
var seqItem = this.mEventSeq[idx];
// Allow unexpected events in primary event sequence.
if (!("unexpected" in this.mEventSeq[idx]))
seqItem.unexpected = false;
if (!("async" in this.mEventSeq[idx]))
this.mEventSeq[idx].async = false;
seqItem.async = false;
// If the event is of unique type (regardless whether it's expected or
// not) then register additional unexpected event that matches to any
// event of the same type with any target different from registered
// expected events.
if (("unique" in seqItem) && seqItem.unique) {
var uniquenessChecker = {
type: seqItem.type,
unexpected: true,
match: function uniquenessChecker_match(aEvent)
{
// The handled event is matched if its target doesn't match to any
// registered expected event.
var matched = true;
for (var idx = 0; idx < this.queue.mEventSeq.length; idx++) {
if (this.queue.isEventExpected(idx) &&
this.queue.compareEvents(idx, aEvent)) {
matched = false;
break;
}
}
return matched;
},
targetDescr: "any target different from expected events",
queue: this
};
this.mEventSeq.push(uniquenessChecker);
}
}
var unexpectedSeq = aInvoker.unexpectedEventSeq;
@ -483,7 +525,7 @@ function eventQueue(aEventType)
msg += " unexpected";
msg += ": event type: " + this.getEventTypeAsString(idx) +
", target: " + this.getEventTargetDescr(idx);
", target: " + this.getEventTargetDescr(idx, true);
gLogger.logToConsole(msg);
gLogger.logToDOM(msg, true);
@ -492,6 +534,10 @@ function eventQueue(aEventType)
if (typeof eventType == "string") {
// DOM event
var target = this.getEventTarget(idx);
if (!target) {
ok(false, "no target for DOM event!");
return;
}
var phase = this.getEventPhase(idx);
target.ownerDocument.addEventListener(eventType, this, phase);
@ -540,10 +586,19 @@ function eventQueue(aEventType)
return this.mEventSeq[aIdx].target;
}
this.getEventTargetDescr = function eventQueue_getEventTargetDescr(aIdx)
this.getEventTargetDescr =
function eventQueue_getEventTargetDescr(aIdx, aDontForceTarget)
{
var descr = this.mEventSeq[aIdx].targetDescr;
return descr ? descr : "no target description";
if (descr)
return descr;
if (aDontForceTarget)
return "no target description";
var target = ("target" in this.mEventSeq[aIdx]) ?
this.mEventSeq[aIdx].target : null;
return prettyName(target);
}
this.getEventPhase = function eventQueue_getEventPhase(aIdx)
@ -574,16 +629,25 @@ function eventQueue(aEventType)
return !this.mEventSeq[aIdx].unexpected;
}
this.compareEvents = function eventQueue_compareEvents(aIdx, aEvent)
this.compareEventTypes = function eventQueue_compareEventTypes(aIdx, aEvent)
{
var eventType1 = this.getEventType(aIdx);
var eventType2 = (aEvent instanceof nsIDOMEvent) ?
aEvent.type : aEvent.eventType;
if (eventType1 != eventType2)
return eventType1 == eventType2;
}
this.compareEvents = function eventQueue_compareEvents(aIdx, aEvent)
{
if (!this.compareEventTypes(aIdx, aEvent))
return false;
// If checker provides "match" function then allow the checker to decide
// whether event is matched.
if ("match" in this.mEventSeq[aIdx])
return this.mEventSeq[aIdx].match(aEvent);
var target1 = this.getEventTarget(aIdx);
if (target1 instanceof nsIAccessible) {
var target2 = (aEvent instanceof nsIDOMEvent) ?
@ -599,7 +663,7 @@ function eventQueue(aEventType)
return target1 == target2;
}
this.isAlreadyCaught = function eventQueue_isAlreadyCaught(aIdx, aEvent)
this.isSameEvent = function eventQueue_isSameEvent(aIdx, aEvent)
{
// We don't have stored info about handled event other than its type and
// target, thus we should filter text change and state change events since
@ -662,11 +726,11 @@ function eventQueue(aEventType)
var emphText = "matched ";
var currType = this.getEventTypeAsString(aExpectedEventIdx);
var currTarget = this.getEventTarget(aExpectedEventIdx);
var currTargetDescr = this.getEventTargetDescr(aExpectedEventIdx);
var consoleMsg = "*****\nEQ matched: " + currType + "\n*****";
gLogger.logToConsole(consoleMsg);
msg += " event, type: " + currType + ", target: " + prettyName(currTarget);
msg += " event, type: " + currType + ", target: " + currTargetDescr;
gLogger.logToDOM(msg, true, emphText);
}
@ -736,31 +800,44 @@ function sequence()
// Event queue invokers
/**
* Invokers defined below take a checker object (or array of checker objects)
* implementing 'check' method which will be called when proper event is
* handled. Invokers listen default event type registered in event queue object
* until it is passed explicetly.
* Invokers defined below take a checker object (or array of checker objects).
* An invoker listens for default event type registered in event queue object
* until its checker is provided.
*
* Note, checker object or array of checker objects is optional.
* Note, you don't need to initialize 'target' and 'type' members of checker
* object. The 'target' member will be initialized by invoker object and you are
* free to use it in 'check' method.
*/
/**
* Click invoker.
*/
function synthClick(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs)
{
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq, aEventType);
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
this.invoke = function synthClick_invoke()
{
// Scroll the node into view, otherwise synth click may fail.
if (this.DOMNode instanceof nsIDOMNSHTMLElement)
this.DOMNode.scrollIntoView(true);
var targetNode = this.DOMNode;
if (targetNode instanceof nsIDOMDocument) {
targetNode =
this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement;
}
synthesizeMouse(this.DOMNode, 1, 1, {});
// Scroll the node into view, otherwise synth click may fail.
if (targetNode instanceof nsIDOMNSHTMLElement) {
targetNode.scrollIntoView(true);
} else if (targetNode instanceof nsIDOMXULElement) {
var targetAcc = getAccessible(targetNode);
targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
}
var x = 1, y = 1;
if (aArgs && ("where" in aArgs) && aArgs.where == "right") {
if (targetNode instanceof nsIDOMNSHTMLElement)
x = targetNode.offsetWidth - 1;
else if (targetNode instanceof nsIDOMXULElement)
x = targetNode.boxObject.width - 1;
}
synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
}
this.finalCheck = function synthClick_finalCheck()
@ -771,16 +848,16 @@ function synthClick(aNodeOrID, aCheckerOrEventSeq, aEventType)
this.getID = function synthClick_getID()
{
return prettyName(aNodeOrID) + " click";
return prettyName(aNodeOrID) + " click";
}
}
/**
* Mouse move invoker.
*/
function synthMouseMove(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthMouseMove(aID, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq, aEventType);
this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
this.invoke = function synthMouseMove_invoke()
{
@ -790,111 +867,163 @@ function synthMouseMove(aNodeOrID, aCheckerOrEventSeq, aEventType)
this.getID = function synthMouseMove_getID()
{
return prettyName(aNodeOrID) + " mouse move";
return prettyName(aID) + " mouse move";
}
}
/**
* General key press invoker.
*/
function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq, aEventType)
function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq, aEventType);
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
this.invoke = function synthKey_invoke()
{
synthesizeKey(this.mKey, this.mArgs);
synthesizeKey(this.mKey, this.mArgs, this.mWindow);
}
this.getID = function synthKey_getID()
{
return prettyName(aNodeOrID) + " '" + this.mKey + "' key";
var key = this.mKey;
switch (this.mKey) {
case "VK_TAB":
key = "tab";
break;
case "VK_DOWN":
key = "down";
break;
case "VK_UP":
key = "up";
break;
case "VK_LEFT":
key = "left";
break;
case "VK_RIGHT":
key = "right";
break;
case "VK_HOME":
key = "home";
break;
case "VK_ESCAPE":
key = "escape";
break;
case "VK_RETURN":
key = "enter";
break;
}
if (aArgs) {
if (aArgs.shiftKey)
key += " shift";
if (aArgs.ctrlKey)
key += " ctrl";
if (aArgs.altKey)
key += " alt";
}
return prettyName(aNodeOrID) + " '" + key + " ' key";
}
this.mKey = aKey;
this.mArgs = aArgs ? aArgs : {};
this.mWindow = aArgs ? aArgs.window : null;
}
/**
* Tab key invoker.
*/
function synthTab(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_TAB", { shiftKey: false },
aCheckerOrEventSeq, aEventType);
this.getID = function synthTab_getID()
{
return prettyName(aNodeOrID) + " tab";
}
this.__proto__ = new synthKey(aNodeOrID, "VK_TAB",
{ shiftKey: false, window: aWindow },
aCheckerOrEventSeq);
}
/**
* Shift tab key invoker.
*/
function synthShiftTab(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthShiftTab(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_TAB", { shiftKey: true },
aCheckerOrEventSeq, aEventType);
aCheckerOrEventSeq);
}
this.getID = function synthTabTest_getID()
{
return prettyName(aNodeOrID) + " shift tab";
}
/**
* Escape key invoker.
*/
function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_ESCAPE", null,
aCheckerOrEventSeq);
}
/**
* Down arrow key invoker.
*/
function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_DOWN", null, aCheckerOrEventSeq,
aEventType);
this.__proto__ = new synthKey(aNodeOrID, "VK_DOWN", aArgs,
aCheckerOrEventSeq);
}
this.getID = function synthDownKey_getID()
{
return prettyName(aNodeOrID) + " key down";
}
/**
* Up arrow key invoker.
*/
function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs,
aCheckerOrEventSeq);
}
/**
* Right arrow key invoker.
*/
function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthRightKey(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", null, aCheckerOrEventSeq,
aEventType);
this.getID = function synthRightKey_getID()
{
return prettyName(aNodeOrID) + " key right";
}
this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", null, aCheckerOrEventSeq);
}
/**
* Home key invoker.
*/
function synthHomeKey(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthHomeKey(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq,
aEventType);
this.getID = function synthHomeKey_getID()
this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
}
/**
* Enter key invoker
*/
function synthEnterKey(aID, aCheckerOrEventSeq)
{
this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
}
/**
* Synth alt + down arrow to open combobox.
*/
function synthOpenComboboxKey(aID, aCheckerOrEventSeq)
{
this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
this.getID = function synthOpenComboboxKey_getID()
{
return prettyName(aNodeOrID) + " key home";
return "open combobox (atl + down arrow) " + prettyName(aID);
}
}
/**
* Focus invoker.
*/
function synthFocus(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthFocus(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq, aEventType);
var checkerOfEventSeq =
aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(aNodeOrID);
this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
this.invoke = function synthFocus_invoke()
{
if (this.DOMNode instanceof Components.interfaces.nsIDOMNSEditableElement ||
if (this.DOMNode instanceof Components.interfaces.nsIDOMNSEditableElement &&
this.DOMNode.editor ||
this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
this.DOMNode.selectionStart = this.DOMNode.selectionEnd = this.DOMNode.value.length;
}
@ -910,28 +1039,164 @@ function synthFocus(aNodeOrID, aCheckerOrEventSeq, aEventType)
/**
* Focus invoker. Focus the HTML body of content document of iframe.
*/
function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(getNode(aNodeOrID).contentDocument,
aCheckerOrEventSeq, aEventType);
var frameDoc = getNode(aNodeOrID).contentDocument;
var checkerOrEventSeq =
aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(frameDoc);
this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);
this.invoke = function synthFocus_invoke()
{
this.DOMNode.body.focus();
}
this.getID = function synthFocus_getID()
{
return prettyName(aNodeOrID) + " frame document focus";
}
}
/**
* Change the current item when the widget doesn't have a focus.
*/
function changeCurrentItem(aID, aItemID)
{
this.eventSeq = [ new nofocusChecker() ];
this.invoke = function changeCurrentItem_invoke()
{
var controlNode = getNode(aID);
var itemNode = getNode(aItemID);
// HTML
if (controlNode.localName == "input") {
if (controlNode.checked)
this.reportError();
controlNode.checked = true;
return;
}
if (controlNode.localName == "select") {
if (controlNode.selectedIndex == itemNode.index)
this.reportError();
controlNode.selectedIndex = itemNode.index;
return;
}
// XUL
if (controlNode.localName == "tree") {
if (controlNode.currentIndex == aItemID)
this.reportError();
controlNode.currentIndex = aItemID;
return;
}
if (controlNode.localName == "menulist") {
if (controlNode.selectedItem == itemNode)
this.reportError();
controlNode.selectedItem = itemNode;
return;
}
if (controlNode.currentItem == itemNode)
ok(false, "Error in test: proposed current item is already current" + prettyName(aID));
controlNode.currentItem = itemNode;
}
this.getID = function changeCurrentItem_getID()
{
return "current item change for " + prettyName(aID);
}
this.reportError = function changeCurrentItem_reportError()
{
ok(false,
"Error in test: proposed current item '" + aItemID + "' is already current");
}
}
/**
* Toggle top menu invoker.
*/
function toggleTopMenu(aID, aCheckerOrEventSeq)
{
this.__proto__ = new synthKey(aID, "VK_ALT", null,
aCheckerOrEventSeq);
this.getID = function toggleTopMenu_getID()
{
return "toggle top menu on " + prettyName(aID);
}
}
/**
* Context menu invoker.
*/
function synthContextMenu(aID, aCheckerOrEventSeq)
{
this.__proto__ = new synthClick(aID, aCheckerOrEventSeq,
{ button: 0, type: "contextmenu" });
this.getID = function synthContextMenu_getID()
{
return "context menu on " + prettyName(aID);
}
}
/**
* Open combobox, autocomplete and etc popup, check expandable states.
*/
function openCombobox(aComboboxID)
{
this.eventSeq = [
new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID)
];
this.invoke = function openCombobox_invoke()
{
getNode(aComboboxID).focus();
synthesizeKey("VK_DOWN", { altKey: true });
}
this.getID = function openCombobox_getID()
{
return "open combobox " + prettyName(aComboboxID);
}
}
/**
* Close combobox, autocomplete and etc popup, check expandable states.
*/
function closeCombobox(aComboboxID)
{
this.eventSeq = [
new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID)
];
this.invoke = function closeCombobox_invoke()
{
synthesizeKey("VK_ESCAPE", { });
}
this.getID = function closeCombobox_getID()
{
return "close combobox " + prettyName(aComboboxID);
}
}
/**
* Select all invoker.
*/
function synthSelectAll(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthSelectAll(aNodeOrID, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq, aEventType);
this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
this.invoke = function synthSelectAll_invoke()
{
@ -970,6 +1235,8 @@ function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
{
if (typeof this.mTarget == "function")
return this.mTarget.call(null, this.mTargetFuncArg);
if (typeof this.mTarget == "string")
return getNode(this.mTarget);
return this.mTarget;
}
@ -1003,6 +1270,25 @@ function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
aTargetFuncArg, true);
}
function focusChecker(aTargetOrFunc, aTargetFuncArg)
{
this.__proto__ = new invokerChecker(EVENT_FOCUS, aTargetOrFunc,
aTargetFuncArg, false);
this.unique = true; // focus event must be unique for invoker action
this.check = function focusChecker_check(aEvent)
{
testStates(aEvent.accessible, STATE_FOCUSED);
}
}
function nofocusChecker(aID)
{
this.__proto__ = new focusChecker(aID);
this.unexpected = true;
}
/**
* Text inserted/removed events checker.
*/
@ -1032,8 +1318,11 @@ function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted)
/**
* Caret move events checker.
*/
function caretMoveChecker(aCaretOffset)
function caretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg)
{
this.__proto__ = new invokerChecker(EVENT_TEXT_CARET_MOVED,
aTargetOrFunc, aTargetFuncArg);
this.check = function caretMoveChecker_check(aEvent)
{
is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset,
@ -1368,28 +1657,18 @@ function sequenceItem(aProcessor, aEventType, aTarget, aItemID)
/**
* Invoker base class for prepare an action.
*/
function synthAction(aNodeOrID, aCheckerOrEventSeq, aEventType)
function synthAction(aNodeOrID, aCheckerOrEventSeq)
{
this.DOMNode = getNode(aNodeOrID);
this.checker = null;
if (aCheckerOrEventSeq) {
if (aCheckerOrEventSeq instanceof Array) {
this.eventSeq = aCheckerOrEventSeq;
} else {
this.checker = aCheckerOrEventSeq;
this.checker.target = this.DOMNode;
this.eventSeq = [ aCheckerOrEventSeq ];
}
}
if (aEventType)
this.eventSeq = [ new invokerChecker(aEventType, this.DOMNode) ];
this.check = function synthAction_check(aEvent)
{
if (this.checker)
this.checker.check(aEvent);
}
this.getID = function synthAction_getID() { return aNodeOrID + " action"; }
this.getID = function synthAction_getID()
{ return prettyName(aNodeOrID) + " action"; }
}

View File

@ -63,11 +63,20 @@ _TEST_FILES =\
test_docload.xul \
test_dragndrop.html \
test_flush.html \
test_focus.html \
test_focus.xul \
test_focus_aria_activedescendant.html \
test_focus_autocomplete.xul \
test_focus_browserui.xul \
test_focus_contextmenu.xul \
test_focus_controls.html \
test_focus_dialog.html \
test_focus_doc.html \
test_focus_general.html \
test_focus_general.xul \
test_focus_listcontrols.xul \
test_focus_menu.xul \
test_focus_name.html \
test_focus_selects.html \
test_focus_tree.xul \
test_focusdoc.html \
test_menu.xul \
test_mutation.html \
test_mutation.xhtml \

View File

@ -22,12 +22,17 @@
const kViaDisplayStyle = 0;
const kViaVisibilityStyle = 1;
function focusMenu(aMenuBarID, aMenuID)
function focusMenu(aMenuBarID, aMenuID, aActiveMenuBarID)
{
this.eventSeq = [
new invokerChecker(EVENT_MENU_START, getNode(aMenuBarID)),
new invokerChecker(EVENT_FOCUS, getNode(aMenuID)),
];
this.eventSeq = [];
if (aActiveMenuBarID) {
this.eventSeq.push(new invokerChecker(EVENT_MENU_END,
getNode(aActiveMenuBarID)));
}
this.eventSeq.push(new invokerChecker(EVENT_MENU_START, getNode(aMenuBarID)));
this.eventSeq.push(new invokerChecker(EVENT_FOCUS, getNode(aMenuID)));
this.invoke = function focusMenu_invoke()
{
@ -143,14 +148,15 @@
var gQueue = null;
//gA11yEventDumpID = "eventdump";
//gA11yEventDumpToConsole = true;
//gA11yEventDumpID = "eventdump"; // debuging
//gA11yEventDumpToConsole = true; // debuging
function doTests()
{
gQueue = new eventQueue();
gQueue.push(new focusMenu("menubar", "menu-file"));
gQueue.push(new focusMenu("menubar2", "menu-help"));
gQueue.push(new focusMenu("menubar", "menu-file", "menubar2"));
gQueue.push(new showMenu("menupopup-file", "menu-file", kViaDisplayStyle));
gQueue.push(new closeMenu("menupopup-file", "menu-file", kViaDisplayStyle));
gQueue.push(new showMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle));
@ -183,6 +189,11 @@
title="Clean up FireAccessibleFocusEvent">
Mozilla Bug 615189
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
@ -205,6 +216,14 @@
</div>
</div>
</div>
<div id="menubar2" role="menubar">
<div id="menu-help" role="menuitem" tabindex="0">
Help
<div id="menupopup-help" role="menu" style="display: none;">
<div id="menuitem-about" role="menuitem" tabindex="0">About</div>
</div>
</div>
</div>
<div tabindex="0" id="outsidemenu">outsidemenu</div>
<div id="eventdump"></div>

View File

@ -23,8 +23,11 @@
/**
* Test "event-from-input" object attribute.
*/
function checker(aValue, aNoTargetID)
function eventFromInputChecker(aEventType, aID, aValue, aNoTargetID)
{
this.type = aEventType;
this.target = getAccessible(aID);
this.noTarget = getNode(aNoTargetID);
this.check = function checker_check(aEvent)
@ -42,16 +45,21 @@
var gQueue = null;
// gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
function doTests()
{
gQueue = new eventQueue();
var id = "textbox", noTargetId = "textarea";
gQueue.push(new synthFocus(id, new checker("false", noTargetId), EVENT_FOCUS));
var checker =
new eventFromInputChecker(EVENT_FOCUS, id, "false", noTargetId);
gQueue.push(new synthFocus(id, checker));
if (!MAC) { // Mac failure is bug 541093
gQueue.push(new synthHomeKey(id, new checker("false", noTargetId), EVENT_TEXT_CARET_MOVED));
var checker =
new eventFromInputChecker(EVENT_TEXT_CARET_MOVED, id, "false", noTargetId);
gQueue.push(new synthHomeKey(id, checker));
}
gQueue.invoke(); // Will call SimpleTest.finish();

View File

@ -20,9 +20,9 @@
/**
* Click checker.
*/
function clickChecker(aCaretOffset, aExtraNodeOrID, aExtraCaretOffset)
function clickChecker(aCaretOffset, aID, aExtraNodeOrID, aExtraCaretOffset)
{
this.__proto__ = new caretMoveChecker(aCaretOffset);
this.__proto__ = new caretMoveChecker(aCaretOffset, aID);
this.extraNode = getNode(aExtraNodeOrID);
@ -62,30 +62,33 @@
testCaretOffset("p", -1);
// test caret move events and caret offsets
gQueue = new eventQueue(EVENT_TEXT_CARET_MOVED);
gQueue = new eventQueue();
var id = "textbox";
gQueue.push(new synthFocus(id, new caretMoveChecker(5)));
gQueue.push(new synthSelectAll(id, new caretMoveChecker(5)));
gQueue.push(new synthClick(id, new caretMoveChecker(0)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1)));
gQueue.push(new synthFocus(id, new caretMoveChecker(5, id)));
gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, id)));
gQueue.push(new synthClick(id, new caretMoveChecker(0, id)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
id = "textarea";
gQueue.push(new synthClick(id, new caretMoveChecker(0)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1)));
gQueue.push(new synthDownKey(id, new caretMoveChecker(12)));
gQueue.push(new synthClick(id, new caretMoveChecker(0, id)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
gQueue.push(new synthDownKey(id, new caretMoveChecker(12, id)));
id = "p";
gQueue.push(new synthClick(id, new caretMoveChecker(0)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1)));
gQueue.push(new synthDownKey(id, new caretMoveChecker(6)));
gQueue.push(new synthClick(id, new caretMoveChecker(0, id)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
gQueue.push(new synthDownKey(id, new caretMoveChecker(6, id)));
gQueue.push(new synthClick("p1_in_div",
new clickChecker(0, "p2_in_div", -1)));
id = "p1_in_div";
gQueue.push(new synthClick(id, new clickChecker(0, id, "p2_in_div", -1)));
gQueue.push(new synthShiftTab("p", new caretMoveChecker(0)));
gQueue.push(new synthShiftTab("textarea", new caretMoveChecker(12)));
gQueue.push(new synthTab("p", new caretMoveChecker(0)));
id = "p";
gQueue.push(new synthShiftTab(id, new caretMoveChecker(0, id)));
id = "textarea";
gQueue.push(new synthShiftTab(id, new caretMoveChecker(12, id)));
id = "p";
gQueue.push(new synthTab(id, new caretMoveChecker(0, id)));
gQueue.invoke(); // Will call SimpleTest.finish();
}

View File

@ -22,7 +22,7 @@
*/
//gA11yEventDumpID = "eventdump"; // debug stuff
gA11yEventDumpToConsole = true;
//gA11yEventDumpToConsole = true;
var gQueue = null;
@ -31,10 +31,10 @@
gQueue = new eventQueue(EVENT_TEXT_CARET_MOVED);
var id = "textbox";
gQueue.push(new synthFocus(id, new caretMoveChecker(5)));
gQueue.push(new synthSelectAll(id, new caretMoveChecker(5)));
gQueue.push(new synthHomeKey(id, new caretMoveChecker(0)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1)));
gQueue.push(new synthFocus(id, new caretMoveChecker(5, id)));
gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, id)));
gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, id)));
gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
gQueue.invoke(); // Will call SimpleTest.finish();
}

View File

@ -1,99 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Accessible focus event testing">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
/**
* Click menu item invoker.
*/
function clickMenuItem(aNodeOrID, aFocusNodeOrID)
{
this.DOMNode = getNode(aFocusNodeOrID);
this.invoke = function clickMenuItem_invoke()
{
synthesizeMouse(getNode(aNodeOrID), 1, 1, {});
}
this.getID = function clickMenuItem_getID()
{
return prettyName(aNodeOrID) + " click menu item";
}
}
/**
* Do tests.
*/
//gA11yEventDumpID = "eventdump"; // debug stuff
var gQueue = null;
function doTests()
{
// Test focus events.
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_FOCUS);
gQueue.push(new synthFocus("textbox"));
gQueue.push(new synthFocus("scale"));
gQueue.push(new synthFocusOnFrame("editabledoc"));
gQueue.push(new synthClick("menu"));
gQueue.push(new synthMouseMove("menuitem"));
gQueue.push(new clickMenuItem("menuitem",
getNode("editabledoc").contentDocument));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=492518"
title="xul:slider accessible of xul:scale is accessible illegally">
Mozilla Bug 492518
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
title=" fire focus event on document accessible whenever the root or body element is focused">
Mozilla Bug 552368
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<textbox id="textbox" value="hello"/>
<scale id="scale" min="0" max="9" value="5"/>
<menubar>
<menu id="menu" label="menu">
<menupopup>
<menuitem id="menuitem" label="menuitem"/>
</menupopup>
</menu>
</menubar>
<iframe id="editabledoc" src="focus.html"/>
<vbox id="eventdump"/>
</vbox>
</hbox>
</window>

View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=429547
-->
<head>
<title>aria-activedescendant focus tests</title>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
//gA11yEventDumpToConsole = true; // debugging
function changeARIAActiveDescendant(aID, aItemID)
{
this.eventSeq = [
new focusChecker(aItemID)
];
this.invoke = function changeARIAActiveDescendant_invoke()
{
getNode(aID).setAttribute("aria-activedescendant", aItemID);
}
this.getID = function changeARIAActiveDescendant_getID()
{
return "change aria-activedescendant on " + aItemID;
}
}
function insertItemNFocus(aID, aNewItemID)
{
this.eventSeq = [
new invokerChecker(EVENT_SHOW, aNewItemID),
new focusChecker(aNewItemID)
];
this.invoke = function insertItemNFocus_invoke()
{
var container = getNode(aID);
var itemNode = document.createElement("div");
itemNode.setAttribute("id", aNewItemID);
itemNode.textContent = "item3";
container.appendChild(itemNode);
container.setAttribute("aria-activedescendant", aNewItemID);
}
this.getID = function insertItemNFocus_getID()
{
return "insert new node and focus it with ID: " + aNewItemID;
}
}
var gQueue = null;
function doTest()
{
gQueue = new eventQueue();
gQueue.push(new synthFocus("container", new focusChecker("item1")));
gQueue.push(new changeARIAActiveDescendant("container", "item2"));
todo(false, "No focus for inserted element, bug 687011");
//gQueue.push(new insertItemNFocus("container", "item3"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
Mozilla Bug 429547
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div role="listbox" aria-activedescendant="item1" id="container" tabindex="1">
<div role="listitem" id="item1">item1</div>
<div role="listitem" id="item2">item2</div>
</div>
</body>
</html>

View File

@ -0,0 +1,463 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/browser.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Accessible focus event testing">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript"
src="../autocomplete.js" />
<script type="application/javascript">
<![CDATA[
////////////////////////////////////////////////////////////////////////////
// Hacky stuffs
// This is the hack needed for searchbar work outside of browser.
function getBrowser()
{
return {
mCurrentBrowser: { engines: new Array() }
};
}
////////////////////////////////////////////////////////////////////////////
// Invokers
function loadFormAutoComplete(aIFrameID)
{
this.iframeNode = getNode(aIFrameID);
this.iframe = getAccessible(aIFrameID);
this.eventSeq = [
new invokerChecker(EVENT_REORDER, this.iframe)
];
this.invoke = function loadFormAutoComplete_invoke()
{
var url = "data:text/html,<html><body><form id='form'>" +
"<input id='input' name='a11ytest-formautocomplete'>" +
"</form></body></html>";
this.iframeNode.setAttribute("src", url);
}
this.getID = function loadFormAutoComplete_getID()
{
return "load form autocomplete page";
}
}
function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue)
{
this.iframe = getAccessible(aIFrameID);
this.eventSeq = [
new invokerChecker(EVENT_REORDER, this.iframe)
];
this.invoke = function initFormAutoCompleteBy_invoke()
{
var iframeDOMDoc = getFormAutoCompleteDOMDoc(aIFrameID);
var inputNode = iframeDOMDoc.getElementById("input");
inputNode.value = aAutoCompleteValue;
var formNode = iframeDOMDoc.getElementById("form");
formNode.submit();
}
this.getID = function initFormAutoCompleteBy_getID()
{
return "init form autocomplete by '" + aAutoCompleteValue + "'";
}
}
function removeChar(aID, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
this.invoke = function removeChar_invoke()
{
synthesizeKey("VK_LEFT", { shiftKey: true });
synthesizeKey("VK_DELETE", {});
}
this.getID = function removeChar_getID()
{
return "remove char on " + prettyName(aID);
}
}
function replaceOnChar(aID, aChar, aCheckerOrEventSeq)
{
this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
this.invoke = function replaceOnChar_invoke()
{
this.DOMNode.select();
synthesizeKey(aChar, {});
}
this.getID = function replaceOnChar_getID()
{
return "replace on char '" + aChar + "' for" + prettyName(aID);
}
}
function focusOnMouseOver(aIDFunc, aIDFuncArg)
{
this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ];
this.invoke = function focusOnMouseOver_invoke()
{
this.id = aIDFunc.call(null, aIDFuncArg);
this.node = getNode(this.id);
this.window = this.node.ownerDocument.defaultView;
if (this.node.localName == "tree") {
var tree = getAccessible(this.node);
var accessible = getAccessible(this.id);
if (tree != accessible) {
var itemX = {}, itemY = {}, treeX = {}, treeY = {};
accessible.getBounds(itemX, itemY, {}, {});
tree.getBounds(treeX, treeY, {}, {});
this.x = itemX.value - treeX.value;
this.y = itemY.value - treeY.value;
}
}
// Generate mouse move events in timeouts until autocomplete popup list
// doesn't have it, the reason is do that because autocomplete popup
// ignores mousemove events firing in too short range.
synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" });
this.doMouseMoveFlood(this);
}
this.finalCheck = function focusOnMouseOver_getID()
{
this.isFlooding = false;
}
this.getID = function focusOnMouseOver_getID()
{
return "mouse over on " + prettyName(aIDFunc.call(null, aIDFuncArg));
}
this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis)
{
synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1,
{ type: "mousemove" }, aThis.window);
if (aThis.isFlooding)
aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis);
}
this.id = null;
this.node = null;
this.window = null;
this.isFlooding = true;
this.x = 1;
this.y = 1;
}
function selectByClick(aIDFunc, aIDFuncArg,
aFocusTargetFunc, aFocusTargetFuncArg)
{
this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ];
this.invoke = function selectByClick_invoke()
{
var id = aIDFunc.call(null, aIDFuncArg);
var node = getNode(id);
var targetWindow = node.ownerDocument.defaultView;
var x = 0, y = 0;
if (node.localName == "tree") {
var tree = getAccessible(node);
var accessible = getAccessible(id);
if (tree != accessible) {
var itemX = {}, itemY = {}, treeX = {}, treeY = {};
accessible.getBounds(itemX, itemY, {}, {});
tree.getBounds(treeX, treeY, {}, {});
x = itemX.value - treeX.value;
y = itemY.value - treeY.value;
}
}
synthesizeMouse(node, x + 1, y + 1, {}, targetWindow);
}
this.getID = function selectByClick_getID()
{
return "select by click " + prettyName(aIDFunc.call(null, aIDFuncArg));
}
}
////////////////////////////////////////////////////////////////////////////
// Target getters
function getItem(aItemObj)
{
var autocomplete = aItemObj.autocomplete;
var autocompleteNode = aItemObj.autocompleteNode;
// XUL searchbar
if (autocompleteNode.localName == "searchbar") {
var popupNode = autocompleteNode._popup;
if (popupNode) {
var list = getAccessible(popupNode);
return list.getChildAt(aItemObj.index);
}
}
// XUL autocomplete
var popupNode = autocompleteNode.popup;
if (!popupNode) {
// HTML form autocomplete
var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
getService(Components.interfaces.nsIAutoCompleteController);
popupNode = controller.input.popup.QueryInterface(nsIDOMNode);
}
if (popupNode) {
if ("richlistbox" in popupNode) {
var list = getAccessible(popupNode.richlistbox);
return list.getChildAt(aItemObj.index);
}
var list = getAccessible(popupNode.tree);
return list.getChildAt(aItemObj.index + 1);
}
}
function getTextEntry(aID)
{
// For form autocompletes the autocomplete widget and text entry widget
// is the same widget, for XUL autocompletes the text entry is a first
// child.
var localName = getNode(aID).localName;
// XUL autocomplete
if (localName == "textbox")
return getAccessible(aID).firstChild;
// HTML form autocomplete
if (localName == "input")
return getAccessible(aID);
// XUL searchbar
if (localName == "searchbar")
return getAccessible(getNode(aID).textbox.inputField);
return null;
}
function itemObj(aID, aIdx)
{
this.autocompleteNode = getNode(aID);
this.autocomplete = this.autocompleteNode.localName == "searchbar" ?
getAccessible(this.autocompleteNode.textbox) :
getAccessible(this.autocompleteNode);
this.index = aIdx;
}
function getFormAutoCompleteDOMDoc(aIFrameID)
{
return getNode(aIFrameID).contentDocument;
}
////////////////////////////////////////////////////////////////////////////
// Test helpers
function queueAutoCompleteTests(aID)
{
// focus autocomplete text entry
gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID)));
// open autocomplete popup
gQueue.push(new synthDownKey(aID, new nofocusChecker()));
// select second option ('hi' option), focus on it
gQueue.push(new synthUpKey(aID,
new focusChecker(getItem, new itemObj(aID, 1))));
// choose selected option, focus on text entry
gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID)));
// remove char, autocomplete popup appears
gQueue.push(new removeChar(aID, new nofocusChecker()));
// select first option ('hello' option), focus on it
gQueue.push(new synthDownKey(aID,
new focusChecker(getItem, new itemObj(aID, 0))));
// mouse move on second option ('hi' option), focus on it
gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1)));
// autocomplete popup updated (no selected item), focus on textentry
gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID)));
// select first option ('hello' option), focus on it
gQueue.push(new synthDownKey(aID,
new focusChecker(getItem, new itemObj(aID, 0))));
// popup gets hidden, focus on textentry
gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID)));
// popup gets open, no focus
gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker()));
// select first option again ('hello' option), focus on it
gQueue.push(new synthDownKey(aID,
new focusChecker(getItem, new itemObj(aID, 0))));
// no option is selected, focus on text entry
gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID)));
// close popup, no focus
gQueue.push(new synthEscapeKey(aID, new nofocusChecker()));
// autocomplete popup appears (no selected item), focus stays on textentry
gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker()));
// mouse move on first option ('hello' option), focus on it
gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0)));
// click first option ('hello' option), popup closes, focus on text entry
gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID));
}
////////////////////////////////////////////////////////////////////////////
// Tests
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
var gInitQueue = null;
function initTests()
{
// register 'test-a11y-search' autocomplete search
initAutoComplete([ "hello", "hi" ],
[ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
gInitQueue = new eventQueue();
gInitQueue.push(new loadFormAutoComplete("iframe"));
gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello"));
gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi"));
gInitQueue.onFinish = function initQueue_onFinish()
{
SimpleTest.executeSoon(doTests);
return DO_NOT_FINISH_TEST;
}
gInitQueue.invoke();
}
var gQueue = null;
function doTests()
{
// Test focus events.
gQueue = new eventQueue();
////////////////////////////////////////////////////////////////////////////
// tree popup autocomplete tests
queueAutoCompleteTests("autocomplete");
////////////////////////////////////////////////////////////////////////////
// richlistbox popup autocomplete tests
queueAutoCompleteTests("richautocomplete");
////////////////////////////////////////////////////////////////////////////
// HTML form autocomplete tests
queueAutoCompleteTests(getFormAutoCompleteDOMDoc("iframe").getElementById("input"));
////////////////////////////////////////////////////////////////////////////
// searchbar tests
// focus searchbar, focus on text entry
gQueue.push(new synthFocus("searchbar",
new focusChecker(getTextEntry, "searchbar")));
// open search engine popup, no focus
gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker()));
// select first item, focus on it
gQueue.push(new synthDownKey("searchbar",
new focusChecker(getItem, new itemObj("searchbar", 0))));
// mouse over on second item, focus on it
gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1)));
// press enter key, focus on text entry
gQueue.push(new synthEnterKey("searchbar",
new focusChecker(getTextEntry, "searchbar")));
// click on search button, open popup, focus goes to document
var searchBtn = getAccessible(getNode("searchbar").searchButton);
gQueue.push(new synthClick(searchBtn, new focusChecker(document)));
// select first item, focus on it
gQueue.push(new synthDownKey("searchbar",
new focusChecker(getItem, new itemObj("searchbar", 0))));
// close popup, focus goes on document
gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document)));
gQueue.onFinish = function()
{
// unregister 'test-a11y-search' autocomplete search
shutdownAutoComplete();
}
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(initTests);
]]>
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=383759"
title="Focus event inconsistent for search box autocomplete">
Mozilla Bug 383759
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<textbox id="autocomplete" type="autocomplete"
autocompletesearch="test-a11y-search"/>
<textbox id="richautocomplete" type="autocomplete"
autocompletesearch="test-a11y-search"
autocompletepopup="richpopup"/>
<panel id="richpopup" type="autocomplete-richlistbox" noautofocus="true"/>
<iframe id="iframe"/>
<searchbar id="searchbar"/>
<vbox id="eventdump"/>
</vbox>
</hbox>
</window>

View File

@ -0,0 +1,178 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Accessibility Loading Document Events Test.">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
<![CDATA[
////////////////////////////////////////////////////////////////////////////
// Helpers
function tabBrowser()
{
return gBrowserWnd.gBrowser;
}
function currentBrowser()
{
return tabBrowser().selectedBrowser;
}
function currentTabDocument()
{
return currentBrowser().contentDocument;
}
function inputInDocument()
{
var tabdoc = currentTabDocument();
return tabdoc.getElementById("input");
}
function urlbarInput()
{
return gBrowserWnd.document.getElementById("urlbar").inputField;
}
////////////////////////////////////////////////////////////////////////////
// Invokers
function loadURI(aURI)
{
this.invoke = function loadURI_invoke()
{
tabBrowser().loadURI(aURI);
}
this.eventSeq = [
new focusChecker(currentTabDocument)
];
this.getID = function loadURI_getID()
{
return "load uri " + aURI;
}
}
function goBack()
{
this.invoke = function goBack_invoke()
{
tabBrowser().goBack();
}
this.eventSeq = [
new focusChecker(inputInDocument)
];
this.getID = function goBack_getID()
{
return "go back one page in history ";
}
}
////////////////////////////////////////////////////////////////////////////
// Testing
var gInputDocURI = "data:text/html,<html><input id='input'></html>";
var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>";
var gBrowserWnd = null;
function loadBrowser()
{
gBrowserWnd = window.openDialog("chrome://browser/content/", "_blank",
"chrome,all,dialog=no", gInputDocURI);
addA11yLoadEvent(startTests, gBrowserWnd);
}
function startTests()
{
// Wait for tab load.
var browser = gBrowserWnd.gBrowser.selectedBrowser;
addA11yLoadEvent(doTests, browser.contentWindow);
}
//gA11yEventDumpToConsole = true; // debug
var gQueue = null;
function doTests()
{
gQueue = new eventQueue();
var tabDocument = currentTabDocument();
var input = inputInDocument();
// move focus to input inside tab document
gQueue.push(new synthTab(tabDocument, new focusChecker(input), gBrowserWnd));
// open new url, focus moves to new document
gQueue.push(new loadURI(gButtonDocURI));
// back one page in history, moves moves on input of tab document
gQueue.push(new goBack());
// open new tab, focus moves to urlbar
gQueue.push(new synthKey(tabDocument, "t", { ctrlKey: true, window: gBrowserWnd },
new focusChecker(urlbarInput)));
// close open tab, focus goes on input of tab document
gQueue.push(new synthKey(tabDocument, "w", { ctrlKey: true, window: gBrowserWnd },
new focusChecker(inputInDocument)));
gQueue.onFinish = function()
{
gBrowserWnd.close();
}
gQueue.invoke();
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(loadBrowser);
]]>
</script>
<vbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=644452"
title="Focus not set when switching to cached document with back or forward if anything other than the document was last focused">
Mozilla Bug 644452
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=665412"
title="Broken focus when returning to editable text field after closing a tab while focused in the Navigation toolbar">
Mozilla Bug 665412
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<vbox id="eventdump"></vbox>
</vbox>
</window>

View File

@ -0,0 +1,78 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Context nenu focus testing">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
var gQueue = null;
function doTests()
{
// Test focus events.
gQueue = new eventQueue();
gQueue.push(new synthFocus("button"));
gQueue.push(new synthContextMenu("button",
new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu")));
gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button")));
gQueue.push(new synthContextMenu("button",
new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu")));
gQueue.push(new synthDownKey("contextmenu", new focusChecker("item1")));
gQueue.push(new synthDownKey("item1", new focusChecker("item2")));
gQueue.push(new synthRightKey("item2", new focusChecker("item2.1")));
gQueue.push(new synthEscapeKey("item2.1", new focusChecker("item2")));
gQueue.push(new synthEscapeKey("item2", new focusChecker("button")));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<button id="button" context="contextmenu" label="button"/>
<menupopup id="contextmenu">
<menuitem id="item1" label="item1"/>
<menu id="item2" label="item2">
<menupopup>
<menuitem id="item2.1" label="item2.1"/>
</menupopup>
</menu>
</menupopup>
<vbox id="eventdump"/>
</vbox>
</hbox>
</window>

View File

@ -0,0 +1,73 @@
<html>
<head>
<title>Accessible focus testing on HTML controls</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
//gA11yEventDumpToConsole = true;
var gQueue = null;
function doTests()
{
gQueue = new eventQueue(EVENT_FOCUS);
gQueue.push(new synthFocus("textbox"));
gQueue.push(new synthFocus("textarea"));
gQueue.push(new synthFocus("button1"));
gQueue.push(new synthFocus("button2"));
gQueue.push(new synthFocus("checkbox"));
gQueue.push(new synthFocus("radio1"));
gQueue.push(new synthDownKey("radio1", new focusChecker("radio2")));
// no focus events for checkbox or radio inputs when they are checked
// programmatically
gQueue.push(new changeCurrentItem("checkbox"));
gQueue.push(new changeCurrentItem("radio1"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<input id="textbox">
<textarea id="textarea"></textarea>
<input id="button1" type="button" value="button">
<button id="button2">button</button>
<input id="checkbox" type="checkbox">
<input id="radio1" type="radio" name="radiogroup">
<input id="radio2" type="radio" name="radiogroup">
<div id="eventdump"></div>
</body>
</html>

View File

@ -15,11 +15,15 @@
src="../common.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
function openCloseDialog(aID)
{
this.DOMNode = getNode(aID);
this.eventSeq = [
new focusChecker(getNode(aID))
];
this.invoke = function openCloseDialog_invoke()
{
@ -29,30 +33,47 @@
this.getID = function openCloseDialog_getID()
{
return "Open close dialog on " + prettyName(aID);
return "Open close dialog while focus on " + prettyName(aID);
}
}
function focusElmWhileSubdocIsFocused(aID)
var gDialogWnd = null;
function getDialogDocument()
{
this.DOMNode = getNode(aID);
return gDialogWnd.document;
}
this.invoke = function focusElmWhileSubdocIsFocused_invoke()
function openDialog(aID)
{
this.eventSeq = [
new focusChecker(getDialogDocument)
];
this.invoke = function openDialog_invoke()
{
this.DOMNode.focus();
gDialogWnd = window.open("focus.html");
}
this.eventSeq = [
new invokerChecker(EVENT_FOCUS, this.DOMNode)
];
this.unexpectedEventSeq = [
new invokerChecker(EVENT_FOCUS, this.DOMNode.ownerDocument)
];
this.getID = function focusElmWhileSubdocIsFocused_getID()
this.getID = function openDialog_getID()
{
return "Focus element while subdocument is focused " + prettyName(aID);
return "Open dialog while focus on " + prettyName(aID);
}
}
function closeDialog(aID)
{
this.eventSeq = [
new focusChecker(aID)
];
this.invoke = function closeDialog_invoke()
{
gDialogWnd.close();
}
this.getID = function closeDialog_getID()
{
return "Close dialog while focus on " + prettyName(aID);
}
}
@ -69,7 +90,7 @@
this.eventSeq = [
new invokerChecker(EVENT_SHOW, this.DOMNode),
new invokerChecker(EVENT_FOCUS, this.DOMNode)
new focusChecker(this.DOMNode)
];
this.getID = function showNFocusAlertDialog_getID()
@ -83,6 +104,7 @@
*/
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
var gQueue = null;
@ -90,18 +112,14 @@
{
gQueue = new eventQueue(EVENT_FOCUS);
gQueue.push(new synthFocus("editablearea"));
gQueue.push(new synthFocus("textbox"));
gQueue.push(new synthFocus("button"));
gQueue.push(new openCloseDialog("button"));
gQueue.push(new openDialog("button"));
gQueue.push(new closeDialog("button"));
var frameNode = getNode("editabledoc");
gQueue.push(new synthFocusOnFrame(frameNode));
gQueue.push(new openCloseDialog(frameNode.contentDocument));
gQueue.push(new focusElmWhileSubdocIsFocused("button"));
gQueue.push(new showNFocusAlertDialog());
gQueue.invoke(); // Will call SimpleTest.finish();
@ -119,11 +137,6 @@
title="focus is not fired for focused document when switching between windows">
Mozilla Bug 551679
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=352220"
title=" Inconsistent focus events when returning to a document frame">
Mozilla Bug 352220
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=580464"
title="Accessible focus incorrect after JS focus() but correct after switching apps or using menu bar">
@ -134,8 +147,6 @@
<pre id="test">
</pre>
<div id="editablearea" contentEditable="true">editable area</div>
<input id="textbox">
<button id="button">button</button>
<iframe id="editabledoc" src="focus.html"></iframe>

View File

@ -7,8 +7,10 @@
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
@ -17,34 +19,10 @@
src="../states.js"></script>
<script type="application/javascript">
/**
* Focus invoker.
*/
function takeFocus(aAcc)
{
this.DOMNode = aAcc; // xxx rename this expected property in events.js
this.invoke = function takeFocus_invoke()
{
this.DOMNode.takeFocus();
};
this.check = function takeFocus_check()
{
testStates(this.DOMNode, STATE_FOCUSABLE | STATE_FOCUSED);
};
this.getID = function takeFocus_getID() { return aAcc.name + " focus"; };
}
/**
* Do tests.
*/
var gQueue = null;
//var gA11yEventDumpID = "eventdump";
//gA11yEventDumpToConsole = true;
function doTests()
{
@ -54,14 +32,23 @@
var frameDocAcc = getAccessible(frameDoc, [nsIAccessibleDocument]);
var buttonAcc = getAccessible("b1");
var frame2Doc = document.getElementById("iframe2").contentDocument;
var frame2Input = frame2Doc.getElementById("input");
var frame2DocAcc = getAccessible(frame2Doc);
var frame2InputAcc = getAccessible(frame2Input);
// Test focus events.
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_FOCUS);
gQueue = new eventQueue();
// try to give focus to contentEditable frame twice to cover bug 512059
gQueue.push(new takeFocus(buttonAcc));
gQueue.push(new takeFocus(frameDocAcc));
gQueue.push(new takeFocus(buttonAcc));
gQueue.push(new takeFocus(frameDocAcc));
gQueue.push(new synthFocus(buttonAcc));
gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc)));
gQueue.push(new synthFocus(buttonAcc));
gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc)));
// focus on not editable document
gQueue.push(new synthFocus(frame2InputAcc));
gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc)));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -83,6 +70,11 @@
title="Accessibility focus event never fired for designMode document after the first focus">
Mozilla Bug 512059
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=618046"
title="No focus change event when Shift+Tab at top of screen">
Mozilla Bug 618046
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
@ -95,6 +87,7 @@
<button id="b1">a button</button>
<iframe id="iframe" src="about:blank"></iframe>
<button id="b2">a button</button>
<iframe id="iframe2" src="data:text/html,<html><input id='input'></html>"></iframe>
</div>
</body>
</html>

View File

@ -0,0 +1,150 @@
<html>
<head>
<title>Accessible focus testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
function focusElmWhileSubdocIsFocused(aID)
{
this.DOMNode = getNode(aID);
this.invoke = function focusElmWhileSubdocIsFocused_invoke()
{
this.DOMNode.focus();
}
this.eventSeq = [
new focusChecker(this.DOMNode)
];
this.unexpectedEventSeq = [
new invokerChecker(EVENT_FOCUS, this.DOMNode.ownerDocument)
];
this.getID = function focusElmWhileSubdocIsFocused_getID()
{
return "Focus element while subdocument is focused " + prettyName(aID);
}
}
function topMenuChecker()
{
this.type = EVENT_FOCUS;
this.match = function topMenuChecker_match(aEvent)
{
return aEvent.accessible.role == ROLE_PARENT_MENUITEM;
}
}
function contextMenuChecker()
{
this.type = EVENT_MENUPOPUP_START;
this.match = function contextMenuChecker_match(aEvent)
{
return aEvent.accessible.role == ROLE_MENUPOPUP;
}
}
function focusContextMenuItemChecker()
{
this.__proto__ = new focusChecker();
this.match = function focusContextMenuItemChecker_match(aEvent)
{
return aEvent.accessible.role == ROLE_MENUITEM;
}
}
/**
* Do tests.
*/
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
var gQueue = null;
function doTests()
{
var frameDoc = document.getElementById("iframe").contentDocument;
var editableDoc = document.getElementById('editabledoc').contentDocument;
editableDoc.designMode = 'on';
gQueue = new eventQueue();
gQueue.push(new synthFocus("editablearea"));
gQueue.push(new synthFocus("navarea"));
gQueue.push(new synthTab("navarea", new focusChecker(frameDoc)));
gQueue.push(new focusElmWhileSubdocIsFocused("link"));
gQueue.push(new synthTab(editableDoc, new focusChecker(editableDoc)));
if (WIN) {
// Alt key is used to active menubar and focus menu item on Windows,
// other platforms requires setting a ui.key.menuAccessKeyFocuses
// preference.
gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker()));
gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc)));
}
gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
todo(false, "shift+tab doesn't issue the focus, see bug 684818");
//gQuee.push(new synthShiftTab("link", new focusChecker("link")));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=352220"
title="Inconsistent focus events when returning to a document frame">
Mozilla Bug 352220
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=550338"
title="Broken focus when returning to editable documents from menus">
Mozilla Bug 550338
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="editablearea" contentEditable="true">editable area</div>
<div id="navarea" tabindex="0">navigable area</div>
<iframe id="iframe" src="data:text/html,<html></html>"></iframe>
<a id="link" href="">link</a>
<iframe id="editabledoc" src="about:blank"></iframe>
<div id="eventdump"></div>
</body>
</html>

View File

@ -0,0 +1,171 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Accessible focus event testing">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
function getColorBtn(aBtnObj)
{
var colorpicker = aBtnObj.colorpicker;
var container = colorpicker.firstChild;
var btn = container.getChildAt(aBtnObj.btnIndex);
return btn;
}
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
var gQueue = null;
function doTests()
{
// Test focus events.
gQueue = new eventQueue();
gQueue.push(new synthFocus("textbox"));
gQueue.push(new synthFocus("textbox_multiline"));
gQueue.push(new synthFocus("scale"));
gQueue.push(new synthFocusOnFrame("editabledoc"));
gQueue.push(new synthFocus("radioclothes",
new focusChecker("radiosweater")));
gQueue.push(new synthDownKey("radiosweater",
new focusChecker("radiojacket")));
gQueue.push(new synthFocus("checkbox"));
gQueue.push(new synthFocus("button"));
gQueue.push(new synthFocus("checkbutton"));
gQueue.push(new synthFocus("radiobutton"));
// focus menubutton
gQueue.push(new synthFocus("menubutton"));
// click menubutton, open popup, focus stays on menu button
gQueue.push(new synthClick("menubutton", new nofocusChecker()));
// select first menu item ("item 1"), focus on menu item
gQueue.push(new synthDownKey("menubutton", new focusChecker("mb_item1")));
// choose select menu item, focus gets back to menubutton
gQueue.push(new synthEnterKey("mb_item1", new focusChecker("menubutton")));
// press enter to open popup, focus stays on menubutton
gQueue.push(new synthEnterKey("menubutton", new nofocusChecker()));
// select second menu item ("item 2"), focus on menu item
gQueue.push(new synthUpKey("menubutton", new focusChecker("mb_item2")));
// clicking on button having associated popup doesn't change a focus
gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
// select first menu item ("item 1"), focus on menu item
gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1")));
// choose select menu item, focus gets back to menubutton
gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton")));
// show popup again for the next test
gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
// click menubutton of the 'menubutton' button while popup of button open.
gQueue.push(new synthClick("mbb", new focusChecker("mbb"), { where: "right" }));
// close popup, focus stays on menubutton, fire focus event
gQueue.push(new synthEscapeKey("mbb", new focusChecker("mbb")));
// click menubutton, open popup, focus stays on menubutton
gQueue.push(new synthClick("mbb", new nofocusChecker(), { where: "right" }));
// select first menu item ("item 1"), focus on menu item
gQueue.push(new synthDownKey("mbb", new focusChecker("mbb_item1")));
// choose select menu item, focus gets back to menubutton
gQueue.push(new synthEnterKey("mbb_item1", new focusChecker("mbb")));
// open popup, focus stays on menubutton
gQueue.push(new synthOpenComboboxKey("mbb", new nofocusChecker()));
// select second menu item ("item 2"), focus on menu item
gQueue.push(new synthUpKey("menubutton", new focusChecker("mbb_item2")));
// click on menu item of menubutton menu, focus menubutton
gQueue.push(new synthClick("mbb_item2", new focusChecker("mbb")));
// focus colorpicker button
gQueue.push(new synthFocus("colorpicker"));
// click on button, open popup, focus goes to current color button
var btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 0 };
var checker = new focusChecker(getColorBtn, btnObj);
gQueue.push(new synthClick("colorpicker", checker));
// select sibling color button, focus on it
btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 1 };
var checker = new focusChecker(getColorBtn, btnObj);
gQueue.push(new synthRightKey("colorpicker", checker));
// choose selected color button, close popup, focus on colorpicker button
gQueue.push(new synthEnterKey("colorpicker", new focusChecker("colorpicker")));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=492518"
title="xul:slider accessible of xul:scale is accessible illegally">
Mozilla Bug 492518
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
title=" fire focus event on document accessible whenever the root or body element is focused">
Mozilla Bug 552368
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<textbox id="textbox" value="hello"/>
<textbox id="textbox_multiline" multiline="true" value="hello"/>
<scale id="scale" min="0" max="9" value="5"/>
<iframe id="editabledoc" src="focus.html"/>
<radiogroup id="radioclothes">
<radio id="radiosweater" label="radiosweater"/>
<radio id="radiocap" label="radiocap" disabled="true"/>
<radio id="radiojacket" label="radiojacket"/>
</radiogroup>
<checkbox id="checkbox" label="checkbox"/>
<button id="button" label="button"/>
<button id="checkbutton" type="checkbox" label="checkbutton"/>
<button id="radiobutton" type="radio" group="rbgroup" label="radio1"/>
<button id="menubutton" type="menu" label="menubutton">
<menupopup>
<menuitem id="mb_item1" label="item1"/>
<menuitem id="mb_item2" label="item2"/>
</menupopup>
</button>
<button id="mbb" type="menu-button" label="menubutton button">
<menupopup>
<menuitem id="mbb_item1" label="item1"/>
<menuitem id="mbb_item2" label="item2"/>
</menupopup>
</button>
<colorpicker id="colorpicker" type="button" label="color picker"
color="#FFFFFF"/>
<popupset>
<menupopup id="backpopup" position="after_start">
<menuitem id="bp_item1" label="Page 1"/>
<menuitem id="bp_item2" label="Page 2"/>
</menupopup>
</popupset>
<button id="popupbutton" label="Pop Me Up" popup="backpopup"/>
<vbox id="eventdump"/>
</vbox>
</hbox>
</window>

View File

@ -0,0 +1,179 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Accessible focus event testing">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
var gQueue = null;
function doTests()
{
// Test focus events.
gQueue = new eventQueue();
gQueue.push(new synthFocus("listbox", new focusChecker("lb_item1")));
gQueue.push(new synthDownKey("lb_item1", new focusChecker("lb_item2")));
gQueue.push(new synthTab("lb_item2", new focusChecker("mslb_item1")));
gQueue.push(new synthDownKey("mslb_item1", new focusChecker("mslb_item2"), { shiftKey: true }));
gQueue.push(new synthTab("mslb_item2", new focusChecker("emptylistbox")));
gQueue.push(new synthFocus("mcolumnlistbox", new focusChecker("mclb_item1")));
gQueue.push(new synthDownKey("mclb_item1", new focusChecker("mclb_item2")));
gQueue.push(new synthFocus("headerlistbox", new focusChecker("hlb_item1")));
gQueue.push(new synthDownKey("hlb_item1", new focusChecker("hlb_item2")));
gQueue.push(new synthFocus("richlistbox", new focusChecker("rlb_item1")));
gQueue.push(new synthDownKey("rlb_item1", new focusChecker("rlb_item2")));
gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1")));
gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true }));
gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox")));
gQueue.push(new synthFocus("menulist"));
gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine")));
gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade")));
gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist")));
gQueue.push(new synthDownKey("menulist", new nofocusChecker("ml_marmalade")));
gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker("ml_marmalade")));
gQueue.push(new synthEnterKey("ml_marmalade", new focusChecker("menulist")));
var textentry = getAccessible("emenulist").firstChild;
gQueue.push(new synthFocus("emenulist", new focusChecker(textentry)));
gQueue.push(new synthDownKey(textentry, new nofocusChecker("eml_tangerine")));
gQueue.push(new synthUpKey(textentry, new focusChecker("eml_marmalade")));
gQueue.push(new synthEnterKey("eml_marmalade", new focusChecker(textentry)));
gQueue.push(new synthOpenComboboxKey("emenulist", new focusChecker("eml_marmalade")));
gQueue.push(new synthEscapeKey("eml_marmalade", new focusChecker(textentry)));
// no focus events for unfocused list controls when current item is
// changed.
gQueue.push(new synthFocus("emptylistbox"));
gQueue.push(new changeCurrentItem("listbox", "lb_item1"));
gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1"));
gQueue.push(new changeCurrentItem("menulist", "ml_tangerine"));
gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418"
title="Accessibles for focused HTML Select elements are not getting focused state">
Mozilla Bug 433418
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893"
title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused">
Mozilla Bug 474893
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
title=" fire focus event on document accessible whenever the root or body element is focused">
Mozilla Bug 552368
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<listbox id="listbox" rows="3">
<listitem id="lb_item1" label="item1"/>
<listitem id="lb_item2" label="item1"/>
</listbox>
<listbox id="multisellistbox" rows="3" seltype="multiple">
<listitem id="mslb_item1" label="item1"/>
<listitem id="mslb_item2" label="item1"/>
</listbox>
<listbox id="emptylistbox" rows="3"/>
<listbox id="mcolumnlistbox" rows="3">
<listcols>
<listcol/>
<listcol/>
</listcols>
<listitem id="mclb_item1">
<listcell label="George"/>
<listcell label="House Painter"/>
</listitem>
<listitem id="mclb_item2">
<listcell label="Mary Ellen"/>
<listcell label="Candle Maker"/>
</listitem>
</listbox>
<listbox id="headerlistbox" rows="3">
<listhead>
<listheader label="Name"/>
<listheader label="Occupation"/>
</listhead>
<listcols>
<listcol/>
<listcol flex="1"/>
</listcols>
<listitem id="hlb_item1">
<listcell label="George"/>
<listcell label="House Painter"/>
</listitem>
<listitem id="hlb_item2">
<listcell label="Mary Ellen"/>
<listcell label="Candle Maker"/>
</listitem>
</listbox>
<richlistbox id="richlistbox">
<richlistitem id="rlb_item1">
<description>A XUL Description!</description>
</richlistitem>
<richlistitem id="rlb_item2">
<button label="A XUL Button"/>
</richlistitem>
</richlistbox>
<richlistbox id="multiselrichlistbox" seltype="multiple">
<richlistitem id="msrlb_item1">
<description>A XUL Description!</description>
</richlistitem>
<richlistitem id="msrlb_item2">
<button label="A XUL Button"/>
</richlistitem>
</richlistbox>
<richlistbox id="emptyrichlistbox" seltype="multiple"/>
<menulist id="menulist">
<menupopup>
<menuitem id="ml_tangerine" label="tangerine trees"/>
<menuitem id="ml_marmalade" label="marmalade skies"/>
</menupopup>
</menulist>
<menulist id="emenulist" editable="true">
<menupopup>
<menuitem id="eml_tangerine" label="tangerine trees"/>
<menuitem id="eml_marmalade" label="marmalade skies"/>
</menupopup>
</menulist>
<vbox id="eventdump"/>
</vbox>
</hbox>
</window>

View File

@ -0,0 +1,115 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Menu focus testing">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
var gQueue = null;
function doTests()
{
// Test focus events.
gQueue = new eventQueue();
if (WIN) {
gQueue.push(new toggleTopMenu("fruit", new focusChecker("fruit")));
gQueue.push(new synthRightKey("fruit", new focusChecker("vehicle")));
gQueue.push(new synthEscapeKey("vehicle", new focusChecker(document)));
}
// mouse move activate items but no focus event until menubar is active
gQueue.push(new synthMouseMove("fruit", new nofocusChecker("apple")));
// mouseover and click on menuitem makes it active before menubar is
// active
gQueue.push(new synthClick("fruit", new focusChecker("fruit")));
// mouseover on menuitem when menubar is active
gQueue.push(new synthMouseMove("apple", new focusChecker("apple")));
// keydown on disabled menuitem (disabled items are skipped on linux)
if (WIN)
gQueue.push(new synthDownKey("apple", new focusChecker("orange")));
// menu and menuitem are both active
// XXX: intermitent failure because two focus events may be coalesced,
// think to workaround or fix this issue, when done enable queue invoker
// below and remove next two.
//gQueue.push(new synthRightKey("apple",
// [ new focusChecker("vehicle"),
// new focusChecker("cycle")]));
gQueue.push(new synthClick("vehicle", new focusChecker("vehicle")));
gQueue.push(new synthMouseMove("cycle", new focusChecker("cycle")));
// open submenu
gQueue.push(new synthRightKey("cycle", new focusChecker("tricycle")));
// move to first menu in cycle, DOMMenuItemActive is fired for fruit,
// cycle and apple menuitems (bug 685191)
todo(false, "focus is fired for 'cycle' menuitem");
//gQueue.push(new synthRightKey("vehicle", new focusChecker("apple")));
// click menuitem to close menu, focus gets back to document
gQueue.push(new synthClick("tricycle", new focusChecker(document)));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
title="Rework accessible focus handling">
Mozilla Bug 673958
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<menubar>
<menu id="fruit" label="Fruit">
<menupopup>
<menuitem id="apple" label="Apple"/>
<menuitem id="orange" label="Orange" disabled="true"/>
</menupopup>
</menu>
<menu id="vehicle" label="Vehicle">
<menupopup>
<menu id="cycle" label="cycle">
<menupopup>
<menuitem id="tricycle" label="tricycle"/>
</menupopup>
</menu>
<menuitem id="car" label="Car" disabled="true"/>
</menupopup>
</menu>
</menubar>
<vbox id="eventdump"/>
</vbox>
</hbox>
</window>

View File

@ -20,8 +20,10 @@
/**
* Checker for invokers.
*/
function actionChecker(aDescription)
function actionChecker(aID, aDescription)
{
this.__proto__ = new invokerChecker(EVENT_FOCUS, aID);
this.check = function actionChecker_check(aEvent)
{
var target = aEvent.accessible;
@ -57,6 +59,7 @@
*/
// gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
var gQueue = null;
@ -86,9 +89,9 @@
gTextboxElm.removeEventListener("blur", gBlurHandler, false);
}
var checker = new actionChecker("It's a tooltip");
gQueue.push(new synthFocus("button", checker));
gQueue.push(new synthTab("textbox", checker));
var descr = "It's a tooltip";
gQueue.push(new synthFocus("button", new actionChecker("button", descr)));
gQueue.push(new synthTab("textbox", new actionChecker("textbox", descr)));
gQueue.invoke(); // Will call SimpleTest.finish();
}

View File

@ -0,0 +1,109 @@
<html>
<head>
<title>Accessible focus testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
var gQueue = null;
function doTests()
{
gQueue = new eventQueue();
// first item is focused until there's selection
gQueue.push(new synthFocus("list", new focusChecker("orange")));
// item is selected and stays focused
gQueue.push(new synthDownKey("list", new nofocusChecker()));
// last selected item is focused
gQueue.push(new synthDownKey("list", new focusChecker("apple"), { shiftKey: true }));
// no focus event if nothing is changed
gQueue.push(new synthDownKey("list", new nofocusChecker("apple")));
// current item is focused
gQueue.push(new synthUpKey("list", new focusChecker("orange"), { ctrlKey: true }));
// focus on empty list (no items to be focused)
gQueue.push(new synthTab("list", new focusChecker("emptylist")));
// current item is focused
gQueue.push(new synthShiftTab("emptylist", new focusChecker("orange")));
// collapsed combobox keeps a focus
gQueue.push(new synthFocus("combobox", new focusChecker("combobox")));
gQueue.push(new synthDownKey("combobox", new nofocusChecker("combobox")));
// selected item is focused for expanded combobox
gQueue.push(new synthOpenComboboxKey("combobox", new focusChecker("cb_apple")));
gQueue.push(new synthUpKey("combobox", new focusChecker("cb_orange")));
// collapsed combobx keeps a focus
gQueue.push(new synthEscapeKey("combobox", new focusChecker("combobox")));
// no focus events for unfocused list controls when current item is
// changed
gQueue.push(new synthFocus("emptylist"));
gQueue.push(new changeCurrentItem("list", "orange"));
gQueue.push(new changeCurrentItem("combobox", "cb_apple"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418"
title="Accessibles for focused HTML Select elements are not getting focused state">
Mozilla Bug 433418
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893"
title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused">
Mozilla Bug 474893
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<select id="list" size="5" multiple="">
<option id="orange">Orange</option>
<option id="apple">Apple</option>
</select>
<select id="emptylist" size="5"></select>
<select id="combobox">
<option id="cb_orange">Orange</option>
<option id="cb_apple">Apple</option>
</select>
<div id="eventdump"></div>
</body>
</html>

View File

@ -18,6 +18,8 @@
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
@ -46,13 +48,13 @@
function focusTree(aTreeID)
{
var checker = new invokerChecker(EVENT_FOCUS, getFirstTreeItem, aTreeID);
var checker = new focusChecker(getFirstTreeItem, aTreeID);
this.__proto__ = new synthFocus(aTreeID, [ checker ]);
}
function moveToNextItem(aTreeID)
{
var checker = new invokerChecker(EVENT_FOCUS, getSecondTreeItem, aTreeID);
var checker = new focusChecker(getSecondTreeItem, aTreeID);
this.__proto__ = new synthDownKey(aTreeID, [ checker ]);
}
@ -83,6 +85,10 @@
gQueue.push(new setTreeView("tree", new nsTableTreeView(5)));
gQueue.push(new focusTree("tree"));
gQueue.push(new moveToNextItem("tree"));
gQueue.push(new synthFocus("emptytree"));
// no focus event for changed current item for unfocused tree
gQueue.push(new changeCurrentItem("tree", 0));
gQueue.invoke();
}
@ -98,7 +104,12 @@
href="https://bugzilla.mozilla.org/show_bug.cgi?id=386821"
title="Need better solution for firing delayed event against xul tree">
Mozilla Bug 386821
</a><br/>
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=406308"
title="Don't fire accessible focus events if widget is not actually in focus, confuses screen readers">
Mozilla Bug 406308
</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
@ -114,6 +125,13 @@
</treecols>
<treechildren id="treechildren"/>
</tree>
<tree id="emptytree" flex="1">
<treecols>
<treecol id="emptytree_col1" flex="1" primary="true" label="column"/>
<treecol id="emptytree_col2" flex="1" label="column 2"/>
</treecols>
<treechildren id="emptytree_treechildren"/>
</tree>
</hbox>
</window>

View File

@ -77,8 +77,8 @@
function focusFileMenu()
{
this.eventSeq = [
new invokerChecker(EVENT_MENU_START, getNode("menubar")),
new invokerChecker(EVENT_FOCUS, getNode("menuitem-file"))
new invokerChecker(EVENT_MENU_START, getNode("menubar"))
// new invokerChecker(EVENT_FOCUS, getNode("menuitem-file")) //intermitent failure
];
this.invoke = function focusFileMenu_invoke()
@ -132,7 +132,7 @@
*/
//gA11yEventDumpID = "eventdump";
gA11yEventDumpToConsole = true;
//gA11yEventDumpToConsole = true;
var gQueue = null;

View File

@ -43,7 +43,7 @@
offset + str.length, str,
isInserted);
if (eventItem[3] == kUnexpected)
if (event[3] == kUnexpected)
this.unexpectedEventSeq.push(checker);
else
this.eventSeq.push(checker);

View File

@ -13,6 +13,8 @@
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
@ -80,7 +82,7 @@
// gets a time for initialization.
gQueue = new eventQueue();
gQueue.push(new synthFocus("input", null, EVENT_FOCUS));
gQueue.push(new synthFocus("input"));
gQueue.push(new spelledTextInvoker("input"));
gQueue.invoke(); // Will call SimpleTest.finish();
}

View File

@ -26,18 +26,13 @@
{
this.accessible = getAccessible(aID);
this.eventSeq = [ new invokerChecker(EVENT_FOCUS, this.accessible) ];
this.eventSeq = [ new focusChecker(this.accessible) ];
this.invoke = function takeFocusInvoker_invoke()
{
this.accessible.takeFocus();
}
this.finalCheck = function takeFocusInvoker()
{
testStates(this.accessible, STATE_FOCUSED);
}
this.getID = function takeFocusInvoker_getID()
{
return "takeFocus for " + prettyName(aID);
@ -51,6 +46,9 @@
gQueue.push(new takeFocusInvoker("aria-link"));
gQueue.push(new takeFocusInvoker("aria-link2"));
gQueue.push(new takeFocusInvoker("link"));
gQueue.push(new takeFocusInvoker("item2"));
gQueue.push(new takeFocusInvoker("plugin"));
gQueue.push(new takeFocusInvoker(document));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -62,11 +60,21 @@
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
Mozilla Bug 429547
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=452710"
title="nsIAccessible::takeFocus testing">
Mozilla Bug 452710
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=646361"
title="No focus event fired on document when focus is set to the document while focused in a plugin">
Mozilla Bug 646361
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
@ -77,5 +85,12 @@
<a id="link" href="">link</span>
<div role="listbox" aria-activedescendant="item1" id="container" tabindex="1">
<div role="listitem" id="item1">item1</div>
<div role="listitem" id="item2">item2</div>
<div role="listitem" id="item3">item3</div>
</div>
<embed id="plugin" type="application/x-test" width="200" height="200" wmode="window"></embed>
</body>
</html>

View File

@ -1,111 +0,0 @@
/**
* Tests an accessible and its children.
* The accessible is one of the following:
* - HTML:select (ROLE_COMBOBOX)
* - HTML:select @size (ROLE_LIST)
* - HTML:label containing either of the above (ROLE_LABEL)
*
* @param aID The ID of the base element to test.
* @param aNames The array of expected names for the accessible and
* its children.
* @param aRoles The array of expected roles for the accessible and
* its children.
* @param aStates The array of states to test for on each accessible.
* @param aUndesiredStates Array of states we don't want to have for each
* accessible.
*
* @note Each of the three arrays must have an equal number of elements. The
* order of elements corresponds to the order in which the tree is walked from
* the base accessible downwards.
*/
function testSelect(aID, aNames, aRoles, aStates, aUndesiredStates)
{
// used to walk the tree starting at the aID's accessible.
var acc = getAccessible(aID);
if (!acc) {
return;
}
testThis(aID, acc, aNames, aRoles, aStates, aUndesiredStates, 0);
}
/**
* Tests a single accessible.
* Recursively calls itself until it reaches the last option item.
* Called first time from the testSelect function.
*
* @param aID @see testSelect
* @param aAcc The accessible to test.
* @param aNames @see testSelect
* @param aRoles @see testSelect
* @param aStates @see testSelect
* @param aUndesiredStates @see testSelect
* @param aIndex The index in the three arrays to use. Used for both
* the error message and for walking the tree downwards
* from the base accessible.
*/
function testThis(aID, aAcc, aNames, aRoles, aStates, aUndesiredStates, aIndex)
{
if (aIndex >= aNames.length)
return; // End of test
else if (!aAcc) {
ok(false, "No accessible for " + aID + " at index " + aIndex + "!");
return;
}
is(aAcc.name, aNames[aIndex],
"wrong name for " + aID + " at index " + aIndex + "!");
var role = getRole(aAcc);
is(role, aRoles[aIndex],
"Wrong role for " + aID + " at index " + aIndex + "!");
testStates(aID, aAcc, aStates, aUndesiredStates, aIndex);
switch(role) {
case ROLE_COMBOBOX:
case ROLE_COMBOBOX_LIST:
case ROLE_LABEL:
case ROLE_LIST:
// All of these expect the next item to be the first child of the current
// accessible.
var acc = null;
try {
acc = aAcc.firstChild;
} catch(e) {}
testThis(aID, acc, aNames, aRoles, aStates, aUndesiredStates, ++aIndex);
break;
case ROLE_COMBOBOX_OPTION:
case ROLE_OPTION:
case ROLE_TEXT_LEAF:
// All of these expect the next item's accessible to be the next sibling.
var acc = null;
try {
acc = aAcc.nextSibling;
} catch(e) {}
testThis(aID, acc, aNames, aRoles, aStates, aUndesiredStates, ++aIndex);
break;
default:
break;
}
}
/**
* Tests the states for the given accessible.
* Does not test for extraStates since we don't need these.
*
* @param aID the ID of the base element.
* @param aAcc the current accessible from the recursive algorithm.
* @param aStates the states array passed down from the testThis function.
* @param aUndesiredStates the array of states we don't want to see, if any.
* @param aIndex the index of the item to test, determined and passed in from
* the testThis function.
*/
function testStates(aID, aAcc, aStates, aUndesiredStates, aIndex)
{
var state = {}, extraState = {};
aAcc.getState(state, extraState);
if (aStates[aIndex] != 0)
is(state.value & aStates[aIndex], aStates[aIndex],
"Wrong state bits for " + aID + " at index " + aIndex + "!");
if (aUndesiredStates[aIndex] != 0)
is(state.value & aUndesiredStates[aIndex], 0,
"Wrong undesired state bits for " + aID + " at index " + aIndex + "!");
}

View File

@ -89,6 +89,16 @@ function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
// Additional test.
// focused/focusable
if (state & STATE_FOCUSED)
isState(state & STATE_FOCUSABLE, STATE_FOCUSABLE, false,
"Focussed " + id + " must be focusable!");
if (aAbsentState && (aAbsentState & STATE_FOCUSABLE)) {
isState(state & STATE_FOCUSED, 0, false,
"Not focusable " + id + " must be not focused!");
}
// readonly/editable
if (state & STATE_READONLY)
isState(extraState & EXT_STATE_EDITABLE, 0, true,
@ -110,7 +120,7 @@ function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
// expanded/collapsed/expandable
if (state & STATE_COLLAPSED || state & STATE_EXPANDED)
isState(extraState & EXT_STATE_EXPANDABLE, EXT_STATE_EXPANDABLE, true,
"Collapsed or expanded " + id + " should be expandable!");
"Collapsed or expanded " + id + " must be expandable!");
if (state & STATE_COLLAPSED)
isState(state & STATE_EXPANDED, 0, false,
@ -120,6 +130,16 @@ function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
isState(state & STATE_COLLAPSED, 0, false,
"Expanded " + id + " cannot be collapsed!");
if (aAbsentState && (extraState & EXT_STATE_EXPANDABLE)) {
if (aAbsentState & STATE_EXPANDED) {
isState(state & STATE_COLLAPSED, STATE_COLLAPSED, false,
"Not expanded " + id + " must be collapsed!");
} else if (aAbsentState & STATE_COLLAPSED) {
isState(state & STATE_EXPANDED, STATE_EXPANDED, false,
"Not collapsed " + id + " must be expanded!");
}
}
// checked/mixed/checkable
if (state & STATE_CHECKED || state & STATE_MIXED)
isState(state & STATE_CHECKABLE, STATE_CHECKABLE, false,
@ -136,7 +156,7 @@ function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
// selected/selectable
if (state & STATE_SELECTED) {
isState(state & STATE_SELECTABLE, STATE_SELECTABLE, false,
"Selected element should be selectable!");
"Selected element must be selectable!");
}
}

View File

@ -49,15 +49,16 @@ _TEST_FILES =\
test_aria.html \
test_aria_imgmap.html \
test_aria_tabs.html \
test_comboboxes.xul \
test_doc.html \
test_docarticle.html \
test_editablebody.html \
test_expandable.xul \
test_frames.html \
test_inputs.html \
test_inputs.xul \
test_link.html \
test_popup.xul \
test_selects.html \
test_stale.html \
test_textbox.xul \
test_tree.xul \

View File

@ -6,56 +6,42 @@
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="nsIAccessible interface for xul:menulist test.">
title="Expanded state change events tests for comboboxes and autocompletes.">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
<script type="application/javascript"
src="../autocomplete.js" />
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
<![CDATA[
function openHideCombobox(aComboboxNodeOrID, aIsOpen)
{
this.invoke = function invoke()
{
synthesizeMouse(this.DOMNode, 5, 5, {});
}
this.check = function check(aEvent)
{
aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
var id = this.getID();
is(aEvent.state, nsIAccessibleStates.STATE_EXPANDED,
"Wrong state change event is handled in test '" + id + "'.");
is(aEvent.isEnabled(), this.mIsOpen,
"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.DOMNodeOrID = aComboboxNodeOrID;
this.DOMNode = getNode(aComboboxNodeOrID);
this.mIsOpen = aIsOpen;
}
//gA11yEventDumpToConsole = true; // debuggin
var gQueue = null;
function doTest()
{
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE);
// register 'test-a11y-search' autocomplete search
initAutoComplete([ "hello", "hi" ],
[ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
var ID = "menulist";
gQueue.push(new openHideCombobox(ID, true));
gQueue.push(new openHideCombobox(ID, false));
gQueue = new eventQueue();
gQueue.push(new openCombobox("menulist"));
gQueue.push(new closeCombobox("menulist"));
todo(false, "Autocompletes don't fire expanded state change events when popup open. See bug 688480!");
//gQueue.push(new openCombobox("autocomplete"));
//gQueue.push(new closeCombobox("autocomplete"));
// XXX: searchbar doesn't fire state change events because accessible
// parent of combobox_list accessible is pushbutton accessible.
@ -64,6 +50,12 @@
//gQueue.push(new openHideCombobox(searchbar, false));
todo(false, "Enable states test for XUL searchbar widget!");
gQueue.onFinish = function()
{
// unregister 'test-a11y-search' autocomplete search
shutdownAutoComplete();
}
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -103,6 +95,9 @@
</menupopup>
</menulist>
<textbox id="autocomplete" type="autocomplete"
autocompletesearch="test-a11y-search"/>
<searchbar id="searchbar"/>
</vbox>
</hbox>

View File

@ -0,0 +1,81 @@
<html>
<head>
<title>HTML selects accessible states tests</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
function doTest()
{
// combobox
var combobox = getAccessible("combobox");
testStates(combobox,
STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSABLE, 0,
STATE_FOCUSED, 0);
var comboboxList = combobox.firstChild;
testStates(comboboxList, 0, 0, STATE_FOCUSABLE, 0);
var opt1 = comboboxList.firstChild;
testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, 0,
STATE_FOCUSED, 0);
var opt2 = comboboxList.lastChild;
testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0,
STATE_FOCUSED, 0);
// listbox
var listbox = getAccessible("listbox");
testStates(listbox, STATE_FOCUSABLE, 0,
STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED, 0);
testStates(listbox.firstChild, STATE_SELECTABLE, 0,
STATE_SELECTED | STATE_FOCUSED | STATE_FOCUSED, 0);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=443889"
title="mochitest for selects and lists">
Mozilla Bug 443889
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=640716"
title="mochitest for selects and lists">
Mozilla Bug 640716
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<select id="combobox">
<option>item 1</option>
<option>item 2</option>
</select>
<select id="listbox" name="component" size="3">
<option>Build</option>
<option>Disability Access APIs</option>
<option>General</option>
<option>UI</option>
</select>
</body>
</html>

View File

@ -1,70 +0,0 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=429547
-->
<head>
<title>aria-activedescendant property chrome tests</title>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="common.js"></script>
<script type="application/javascript">
const ELEMENT_NODE = nsIDOMNode.ELEMENT_NODE;
var gContainerFocused = false;
function doTest()
{
var focusHandler = {
handleEvent: function handleEvent(aEvent) {
var target = aEvent.target;
if (target.nodeType == ELEMENT_NODE &&
target.getAttribute("id") == "container")
gContainerFocused = true;
}
};
var container = document.getElementById("container");
container.addEventListener("focus", focusHandler, false);
var item2Acc = getAccessible("item2");
if (item2Acc) {
item2Acc.takeFocus();
ok(gContainerFocused,
"Container with active descendant didn't receive the focus.");
}
container.removeEventListener("focus", focusHandler, false);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
Mozilla Bug 429547
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div aria-activedescendant="item1" id="container" tabindex="1">
<div id="item1">item1</div>
<div id="item2">item2</div>
<div id="item3">item3</div>
</div>
</body>
</html>

View File

@ -1,206 +0,0 @@
<html>
<head>
<title>nsIAccessible selects tests</title>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="common.js"></script>
<script type="application/javascript"
src="role.js"></script>
<script type="application/javascript"
src="states.js"></script>
<script type="application/javascript"
src="nsIAccessible_selects.js"></script>
<script type="application/javascript">
function doTest()
{
// Label and combo, separate tags
var names = [
"Foo:", // combobox
"Foo:", // combobox list
"item 1", // first item
"item 2" // second item
];
var roles = [
ROLE_COMBOBOX, // root
ROLE_COMBOBOX_LIST, // list accessible
ROLE_COMBOBOX_OPTION, // first option
ROLE_COMBOBOX_OPTION // second option
];
var states = [
(STATE_FOCUSABLE | STATE_HASPOPUP | STATE_COLLAPSED), // combobox
(0), // combobox_list
(STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE | STATE_FOCUSED),
(STATE_SELECTABLE | STATE_FOCUSABLE) // second item, not focused
];
var undesiredStates = [
(STATE_FOCUSED), // combobox
(STATE_FOCUSED), // combobox_list
(0), // first item
(STATE_SELECTED | STATE_FOCUSED) // second, not currently focused, item
];
testSelect("combo1", names, roles, states, undesiredStates);
// Select nested within label element.
names = [
"Search in: Newsticker", // label
"Search in:", // text leaf
"Search in:", // combobox
"Search in: Newsticker", // combobox_list
"Newsticker", // option 1
"Entire site" // Option 2
];
roles = [
ROLE_LABEL, // root
ROLE_TEXT_LEAF, // inner text
ROLE_COMBOBOX, // combobox accessible
ROLE_COMBOBOX_LIST, // list accessible
ROLE_COMBOBOX_OPTION, // first option
ROLE_COMBOBOX_OPTION // second option
];
states = [
(0), // label
(0), // text leaf
(STATE_FOCUSABLE | STATE_HASPOPUP | STATE_COLLAPSED), // combobox
(0), // combobox_list
(STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE | STATE_FOCUSED),
(STATE_SELECTABLE | STATE_FOCUSABLE) // second item, not focused
];
undesiredStates = [
(0), // label
(0), // text leaf
(STATE_FOCUSED), // combobox
(STATE_FOCUSED), // combobox_list
(0), // first item
(STATE_SELECTED | STATE_FOCUSED) // second, not currently focused, item
];
testSelect("combo2", names, roles, states, undesiredStates);
// select @size with label as separate tags.
names = [
"Component:", // list
"Build", // item 1
"Disability Access APIs", // item 2
"General", // item 3
"UI" // item 4
];
roles = [
ROLE_LISTBOX, // root
ROLE_OPTION, // item 1
ROLE_OPTION, // item 2
ROLE_OPTION, // item 4
ROLE_OPTION // item 4
];
states = [
(STATE_FOCUSABLE), // list
(STATE_SELECTABLE | STATE_FOCUSABLE | STATE_FOCUSED),
(STATE_SELECTABLE | STATE_FOCUSABLE), // second item, not focused
(STATE_SELECTABLE | STATE_FOCUSABLE), // third item, not focused
(STATE_SELECTABLE | STATE_FOCUSABLE) // fourth item, not focused
];
undesiredStates = [
(STATE_FOCUSED), // listbox
(STATE_SELECTED), // first item
(STATE_SELECTED | STATE_FOCUSED), // second, not currently focused, item
(STATE_SELECTED | STATE_FOCUSED), // third, not currently focused, item
(STATE_SELECTED | STATE_FOCUSED) // fourth, not currently focused, item
];
testSelect("list1", names, roles, states, undesiredStates);
// Select @size nested within label element.
names = [
"Version:", // label
"Version:", // text leaf
"Version:", // list
"2.0", // option 1
"3.0", // Option 2
"3.1", // Option 3
"trunk" // Option 4
];
roles = [
ROLE_LABEL, // root
ROLE_TEXT_LEAF, // inner text
ROLE_LISTBOX, // listbox accessible
ROLE_OPTION, // first option
ROLE_OPTION, // second option
ROLE_OPTION, // third option
ROLE_OPTION // fourth option
];
states = [
(0), // label
(0), // text leaf
(STATE_FOCUSABLE), // listbox
(STATE_SELECTABLE | STATE_FOCUSABLE | STATE_FOCUSED), // Option 1
(STATE_SELECTABLE | STATE_FOCUSABLE), // second item, not focused
(STATE_SELECTABLE | STATE_FOCUSABLE), // third item, not focused
(STATE_SELECTABLE | STATE_FOCUSABLE) // fourth item, not focused
];
undesiredStates = [
(0), // label
(0), // text leaf
(STATE_FOCUSED | STATE_HASPOPUP | STATE_COLLAPSED), // listbox
(STATE_SELECTED), // first item
(STATE_SELECTED | STATE_FOCUSED), // second, not currently focused, item
(STATE_SELECTED | STATE_FOCUSED), // third, not currently focused, item
(STATE_SELECTED | STATE_FOCUSED) // fourth, not currently focused, item
];
testSelect("list2", names, roles, states, undesiredStates);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=443889"
title="mochitest for selects and lists">
Mozilla Bug 443889
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<form action="post.php" method="post">
<!-- Label and select separate tags -->
<label for="combo1">Foo:</label>
<select id="combo1" name="combo1">
<option>item 1</option>
<option>item 2</option>
</select><br />
<!-- Select embedded in label -->
<label id="combo2">Search in:<select name="search">
<option>Newsticker</option>
<option>Entire site</option>
</select></label><br />
<!-- Label and select @size -->
<label for="list1">Component:</label>
<select id="list1" name="component" size="3">
<option>Build</option>
<option>Disability Access APIs</option>
<option>General</option>
<option>UI</option>
</select><br />
<!-- Select @size nested within label -->
<label id="list2">Version:<select name="version" size="3">
<option>2.0</option>
<option>3.0</option>
<option>3.1</option>
<option>trunk</option>
</select></label><br />
</form>
</body>
</html>

View File

@ -31,8 +31,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=441519
if (outerDocAcc) {
testRole(outerDocAcc, ROLE_INTERNAL_FRAME);
// check if it is focusable, not desired.
testStates(outerDocAcc, 0, 0, STATE_FOCUSABLE);
// check if it is focusable.
testStates(outerDocAcc, STATE_FOCUSABLE, 0);
// see bug 428954: No name wanted for internal frame
is(outerDocAcc.name, null, "Wrong name for internal frame!");

View File

@ -50,6 +50,10 @@
testValue("aria_main_link", href);
testValue("aria_navigation_link", href);
// HTML controls
testValue("combobox1", "item1");
testValue("combobox2", "item2");
SimpleTest.finish();
}
@ -84,5 +88,13 @@
<!-- strange edge case: please don't do this in the wild -->
<a id="aria_link_link" role="link" href="foo">link</a>
<select id="combobox1">
<option id="cb1_item1">item1</option>
<option id="cb1_item2">item2</option>
</select>
<select id="combobox2">
<option id="cb2_item1">item1</option>
<option id="cb2_item2" selected="true">item2</option>
</select>
</body>
</html>

View File

@ -90,6 +90,10 @@
#include "nsIDOMXULMenuListElement.h"
#endif
#ifdef ACCESSIBILITY
#include "nsAccessibilityService.h"
#endif
using namespace mozilla;
using namespace mozilla::dom;
@ -1892,6 +1896,16 @@ nsFocusManager::SendFocusOrBlurEvent(PRUint32 aType,
return;
}
#ifdef ACCESSIBILITY
nsAccessibilityService* accService = GetAccService();
if (accService) {
if (aType == NS_FOCUS_CONTENT)
accService->NotifyOfDOMFocus(aTarget);
else
accService->NotifyOfDOMBlur(aTarget);
}
#endif
nsContentUtils::AddScriptRunner(
new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
aWindowRaised, aIsRefocus));

View File

@ -69,6 +69,12 @@ public:
*/
virtual PRInt32 GetSelectedIndex() = 0;
/**
* Return current option. The current option is the option displaying
* the focus ring when the listbox is focused.
*/
virtual already_AddRefed<nsIContent> GetCurrentOption() = 0;
/**
* Initiates mouse capture for the listbox
*

View File

@ -240,78 +240,15 @@ void nsListControlFrame::PaintFocus(nsRenderingContext& aRC, nsPoint aPt)
{
if (mFocused != this) return;
// The mEndSelectionIndex is what is currently being selected
// use the selected index if this is kNothingSelected
PRInt32 focusedIndex;
if (mEndSelectionIndex == kNothingSelected) {
focusedIndex = GetSelectedIndex();
} else {
focusedIndex = mEndSelectionIndex;
}
nsPresContext* presContext = PresContext();
nsIFrame* containerFrame = GetOptionsContainer();
if (!containerFrame) return;
nsIFrame * childframe = nsnull;
nsresult result = NS_ERROR_FAILURE;
nsCOMPtr<nsIContent> focusedContent;
nsRefPtr<nsHTMLSelectElement> selectElement =
nsHTMLSelectElement::FromContent(mContent);
NS_ASSERTION(selectElement, "Can't be null");
// If we have a selected index then get that child frame
if (focusedIndex != kNothingSelected) {
focusedContent = GetOptionContent(focusedIndex);
// otherwise we find the focusedContent's frame and scroll to it
if (focusedContent) {
childframe = focusedContent->GetPrimaryFrame();
}
} else {
// Since there isn't a selected item we need to show a focus ring around the first
// non-disabled item and skip all the option group elements (nodes)
nsCOMPtr<nsIDOMNode> node;
PRUint32 length;
selectElement->GetLength(&length);
if (length) {
// find the first non-disabled item
PRBool isDisabled = PR_TRUE;
for (PRUint32 i = 0; i < length && isDisabled; i++) {
if (NS_FAILED(selectElement->Item(i, getter_AddRefs(node))) || !node) {
break;
}
if (NS_FAILED(selectElement->IsOptionDisabled(i, &isDisabled))) {
break;
}
if (isDisabled) {
node = nsnull;
} else {
break;
}
}
if (!node) {
return;
}
}
// if we found a node use it, if not get the first child (this is for empty selects)
if (node) {
focusedContent = do_QueryInterface(node);
childframe = focusedContent->GetPrimaryFrame();
}
if (!childframe) {
// Failing all else, try the first thing we have, but only if
// it's an element. Text frames need not apply.
childframe = containerFrame->GetFirstPrincipalChild();
if (childframe && !childframe->GetContent()->IsElement()) {
childframe = nsnull;
}
result = NS_OK;
}
nsIFrame* childframe = nsnull;
nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
if (focusedContent) {
childframe = focusedContent->GetPrimaryFrame();
}
nsRect fRect;
@ -820,9 +757,20 @@ nsListControlFrame::SingleSelection(PRInt32 aClickedIndex, PRBool aDoToggle)
PR_TRUE, PR_TRUE);
}
ScrollToIndex(aClickedIndex);
#ifdef ACCESSIBILITY
bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
#endif
mStartSelectionIndex = aClickedIndex;
mEndSelectionIndex = aClickedIndex;
InvalidateFocus();
#ifdef ACCESSIBILITY
if (isCurrentOptionChanged) {
FireMenuItemActiveEvent();
}
#endif
return wasChanged;
}
@ -915,11 +863,18 @@ nsListControlFrame::PerformSelection(PRInt32 aClickedIndex,
if (mStartSelectionIndex == kNothingSelected) {
mStartSelectionIndex = aClickedIndex;
mEndSelectionIndex = aClickedIndex;
} else {
mEndSelectionIndex = aClickedIndex;
}
#ifdef ACCESSIBILITY
bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
#endif
mEndSelectionIndex = aClickedIndex;
InvalidateFocus();
#ifdef ACCESSIBILITY
if (isCurrentOptionChanged) {
FireMenuItemActiveEvent();
}
#endif
} else if (aIsControl) {
wasChanged = SingleSelection(aClickedIndex, PR_TRUE);
} else {
@ -1321,6 +1276,55 @@ nsListControlFrame::GetSelectedIndex()
return aIndex;
}
already_AddRefed<nsIContent>
nsListControlFrame::GetCurrentOption()
{
// The mEndSelectionIndex is what is currently being selected. Use
// the selected index if this is kNothingSelected.
PRInt32 focusedIndex = (mEndSelectionIndex == kNothingSelected) ?
GetSelectedIndex() : mEndSelectionIndex;
if (focusedIndex != kNothingSelected) {
return GetOptionContent(focusedIndex);
}
nsRefPtr<nsHTMLSelectElement> selectElement =
nsHTMLSelectElement::FromContent(mContent);
NS_ASSERTION(selectElement, "Can't be null");
// There is no a selected item return the first non-disabled item and skip all
// the option group elements.
nsCOMPtr<nsIDOMNode> node;
PRUint32 length;
selectElement->GetLength(&length);
if (length) {
PRBool isDisabled = PR_TRUE;
for (PRUint32 i = 0; i < length && isDisabled; i++) {
if (NS_FAILED(selectElement->Item(i, getter_AddRefs(node))) || !node) {
break;
}
if (NS_FAILED(selectElement->IsOptionDisabled(i, &isDisabled))) {
break;
}
if (isDisabled) {
node = nsnull;
} else {
break;
}
}
if (!node) {
return nsnull;
}
}
if (node) {
nsCOMPtr<nsIContent> focusedOption = do_QueryInterface(node);
return focusedOption.forget();
}
return nsnull;
}
PRBool
nsListControlFrame::IsInDropDownMode() const
{
@ -1983,19 +1987,7 @@ nsListControlFrame::FireMenuItemActiveEvent()
return;
}
// The mEndSelectionIndex is what is currently being selected
// use the selected index if this is kNothingSelected
PRInt32 focusedIndex;
if (mEndSelectionIndex == kNothingSelected) {
focusedIndex = GetSelectedIndex();
} else {
focusedIndex = mEndSelectionIndex;
}
if (focusedIndex == kNothingSelected) {
return;
}
nsCOMPtr<nsIContent> optionContent = GetOptionContent(focusedIndex);
nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
if (!optionContent) {
return;
}
@ -2104,11 +2096,6 @@ nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
mButtonDown = PR_TRUE;
CaptureMouseEvents(PR_TRUE);
mChangesSinceDragStart = HandleListSelection(aMouseEvent, selectedIndex);
#ifdef ACCESSIBILITY
if (mChangesSinceDragStart) {
FireMenuItemActiveEvent();
}
#endif
} else {
// NOTE: the combo box is responsible for dropping it down
if (mComboboxFrame) {
@ -2601,9 +2588,6 @@ nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
if (!UpdateSelection()) {
return NS_OK;
}
#ifdef ACCESSIBILITY
FireMenuItemActiveEvent(); // Only fire if new item reached
#endif
}
break;
}
@ -2633,6 +2617,10 @@ nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
mEndSelectionIndex = newIndex;
InvalidateFocus();
ScrollToIndex(newIndex);
#ifdef ACCESSIBILITY
FireMenuItemActiveEvent();
#endif
} else if (mControlSelectMode && charcode == ' ') {
wasChanged = SingleSelection(newIndex, PR_TRUE);
} else {
@ -2644,11 +2632,6 @@ nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
return NS_OK;
}
}
#ifdef ACCESSIBILITY
if (charcode != ' ') {
FireMenuItemActiveEvent();
}
#endif
}
return NS_OK;

View File

@ -152,7 +152,8 @@ public:
// nsIListControlFrame
virtual void SetComboboxFrame(nsIFrame* aComboboxFrame);
virtual PRInt32 GetSelectedIndex();
virtual PRInt32 GetSelectedIndex();
virtual already_AddRefed<nsIContent> GetCurrentOption();
/**
* Gets the text of the currently selected item.

View File

@ -78,6 +78,10 @@ NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame)
NS_QUERYFRAME_HEAD(nsMenuBarFrame)
NS_QUERYFRAME_ENTRY(nsMenuBarFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
//
// nsMenuBarFrame cntr
//

View File

@ -60,6 +60,8 @@ nsIFrame* NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
class nsMenuBarFrame : public nsBoxFrame, public nsMenuParent
{
public:
NS_DECL_QUERYFRAME_TARGET(nsMenuBarFrame)
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS
nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext);

View File

@ -104,6 +104,10 @@ NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
//
// nsMenuPopupFrame ctor
//

View File

@ -130,6 +130,8 @@ class nsMenuPopupFrame;
class nsMenuPopupFrame : public nsBoxFrame, public nsMenuParent
{
public:
NS_DECL_QUERYFRAME_TARGET(nsMenuPopupFrame)
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS
nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext);

View File

@ -672,7 +672,7 @@ NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(PRInt32 aIndex)
if (aIndex != -1)
mTree->InvalidateRow(aIndex);
// Fire DOMMenuItemActive event for tree
// Fire DOMMenuItemActive or DOMMenuItemInactive event for tree.
nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mTree);
NS_ASSERTION(boxObject, "no box object!");
if (!boxObject)
@ -683,12 +683,13 @@ NS_IMETHODIMP nsTreeSelection::SetCurrentIndex(PRInt32 aIndex)
nsCOMPtr<nsINode> treeDOMNode(do_QueryInterface(treeElt));
NS_ENSURE_STATE(treeDOMNode);
nsRefPtr<nsPLDOMEvent> event =
new nsPLDOMEvent(treeDOMNode, NS_LITERAL_STRING("DOMMenuItemActive"),
PR_TRUE, PR_FALSE);
if (!event)
return NS_ERROR_OUT_OF_MEMORY;
NS_NAMED_LITERAL_STRING(DOMMenuItemActive, "DOMMenuItemActive");
NS_NAMED_LITERAL_STRING(DOMMenuItemInactive, "DOMMenuItemInactive");
nsRefPtr<nsPLDOMEvent> event =
new nsPLDOMEvent(treeDOMNode,
(aIndex != -1 ? DOMMenuItemActive : DOMMenuItemInactive),
PR_TRUE, PR_FALSE);
return event->PostDOMEvent();
}