diff --git a/mozilla/accessible/src/base/nsAccessibilityAtomList.h b/mozilla/accessible/src/base/nsAccessibilityAtomList.h index f23f7571646..b1c64b2f02c 100755 --- a/mozilla/accessible/src/base/nsAccessibilityAtomList.h +++ b/mozilla/accessible/src/base/nsAccessibilityAtomList.h @@ -121,10 +121,11 @@ ACCESSIBILITY_ATOM(type, "type") ACCESSIBILITY_ATOM(value, "value") // DHTML accessibility attributes -ACCESSIBILITY_ATOM(valuenow, "valuenow") // For DHTML widget values -ACCESSIBILITY_ATOM(role, "role") ACCESSIBILITY_ATOM(checked, "checked") ACCESSIBILITY_ATOM(expanded, "expanded") -ACCESSIBILITY_ATOM(required, "required") ACCESSIBILITY_ATOM(invalid, "invalid") +ACCESSIBILITY_ATOM(multiselect, "multiselect") +ACCESSIBILITY_ATOM(required, "required") +ACCESSIBILITY_ATOM(role, "role") ACCESSIBILITY_ATOM(selected, "selected") +ACCESSIBILITY_ATOM(valuenow, "valuenow") // For DHTML widget values diff --git a/mozilla/accessible/src/base/nsAccessible.cpp b/mozilla/accessible/src/base/nsAccessible.cpp index ca7a6d5369b..f4b28771de9 100644 --- a/mozilla/accessible/src/base/nsAccessible.cpp +++ b/mozilla/accessible/src/base/nsAccessible.cpp @@ -92,6 +92,7 @@ #include "nsIImageLoadingContent.h" #include "nsITimer.h" #include "nsIDOMHTMLDocument.h" +#include "nsArray.h" #ifdef NS_DEBUG #include "nsIFrameDebug.h" @@ -105,7 +106,52 @@ //----------------------------------------------------- // construction //----------------------------------------------------- -NS_IMPL_ISUPPORTS_INHERITED2(nsAccessible, nsAccessNode, nsIAccessible, nsPIAccessible) +NS_IMPL_ADDREF_INHERITED(nsAccessible, nsAccessNode) +NS_IMPL_RELEASE_INHERITED(nsAccessible, nsAccessNode) + +nsresult nsAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr) +{ + // Custom-built QueryInterface() knows when we support nsIAccessibleSelectable + // based on xhtml2:role and waistate:multiselect + *aInstancePtr = nsnull; + + if (aIID.Equals(NS_GET_IID(nsIAccessible))) { + *aInstancePtr = NS_STATIC_CAST(nsIAccessible*, this); + NS_ADDREF_THIS(); + return NS_OK; + } + + if(aIID.Equals(NS_GET_IID(nsPIAccessible))) { + *aInstancePtr = NS_STATIC_CAST(nsPIAccessible*, this); + NS_ADDREF_THIS(); + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIAccessibleSelectable))) { + nsCOMPtr content(do_QueryInterface(mDOMNode)); + if (!content) { + return NS_ERROR_FAILURE; // This accessible has been shut down + } + if (!content->HasAttr(kNameSpaceID_XHTML2_Unofficial, + nsAccessibilityAtoms::role)) { + // If we have an XHTML role attribute present and the + // waistate multiselect attribute not empty or false, then we need + // to support nsIAccessibleSelectable + // If either attribute (role or multiselect) change, then we'll + // destroy this accessible so that we can follow COM identity rules. + nsAutoString multiSelect; + content->GetAttr(kNameSpaceID_StatesWAI_Unofficial, + nsAccessibilityAtoms::multiselect, + multiSelect); + if (!multiSelect.IsEmpty() && !multiSelect.EqualsLiteral("false")) { + *aInstancePtr = NS_STATIC_CAST(nsIAccessibleSelectable*, this); + NS_ADDREF_THIS(); + } + } + } + + return nsAccessNode::QueryInterface(aIID, aInstancePtr); +} nsAccessible::nsAccessible(nsIDOMNode* aNode, nsIWeakReference* aShell): nsAccessNodeWrap(aNode, aShell), mParent(nsnull), mFirstChild(nsnull), mNextSibling(nsnull), mRoleMapEntry(nsnull) @@ -968,12 +1014,73 @@ nsIFrame* nsAccessible::GetBoundsFrame() return GetFrame(); } +already_AddRefed +nsAccessible::GetMultiSelectFor(nsIDOMNode *aNode) +{ + NS_ENSURE_TRUE(aNode, nsnull); + nsCOMPtr accService = + do_GetService("@mozilla.org/accessibilityService;1"); + NS_ENSURE_TRUE(accService, nsnull); + nsCOMPtr accessible; + accService->GetAccessibleFor(aNode, getter_AddRefs(accessible)); + if (!accessible) { + return nsnull; + } + + PRUint32 state; + accessible->GetFinalState(&state); + if (0 == (state & STATE_SELECTABLE)) { + return nsnull; + } + + PRUint32 containerRole; + while (0 == (state & STATE_MULTISELECTABLE)) { + nsIAccessible *current = accessible; + current->GetParent(getter_AddRefs(accessible)); + if (!accessible || (NS_SUCCEEDED(accessible->GetFinalRole(&containerRole)) && + containerRole == ROLE_PANE)) { + return nsnull; + } + accessible->GetFinalState(&state); + } + nsIAccessible *returnAccessible = nsnull; + accessible.swap(returnAccessible); + return returnAccessible; +} + +nsresult nsAccessible::SetNonTextSelection(PRBool aSelect) +{ + nsCOMPtr multiSelect = GetMultiSelectFor(mDOMNode); + if (!multiSelect) { + return aSelect ? TakeFocus() : NS_ERROR_FAILURE; + } + nsCOMPtr content(do_QueryInterface(mDOMNode)); + NS_ASSERTION(content, "Called for dead accessible"); + + // For DHTML widgets use WAI namespace + PRUint32 nameSpaceID = mRoleMapEntry ? kNameSpaceID_StatesWAI_Unofficial : kNameSpaceID_None; + if (aSelect) { + return content->SetAttr(nameSpaceID, nsAccessibilityAtoms::selected, NS_LITERAL_STRING("true"), PR_TRUE); + } + return content->UnsetAttr(nameSpaceID, nsAccessibilityAtoms::selected, PR_TRUE); +} + /* void removeSelection (); */ NS_IMETHODIMP nsAccessible::RemoveSelection() { + if (!mDOMNode) { + return NS_ERROR_FAILURE; + } + + PRUint32 state; + GetFinalState(&state); + if (state & STATE_SELECTABLE) { + return SetNonTextSelection(PR_TRUE); + } + nsCOMPtr control(do_QueryReferent(mWeakShell)); if (!control) { - return NS_ERROR_FAILURE; + return NS_ERROR_FAILURE; } nsCOMPtr selection; @@ -996,9 +1103,20 @@ NS_IMETHODIMP nsAccessible::RemoveSelection() /* void takeSelection (); */ NS_IMETHODIMP nsAccessible::TakeSelection() { + if (!mDOMNode) { + return NS_ERROR_FAILURE; + } + + PRUint32 state; + GetFinalState(&state); + if (state & STATE_SELECTABLE) { + return SetNonTextSelection(PR_TRUE); + } + nsCOMPtr control(do_QueryReferent(mWeakShell)); - if (!control) + if (!control) { return NS_ERROR_FAILURE; + } nsCOMPtr selection; nsresult rv = control->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); @@ -2067,6 +2185,177 @@ nsresult nsAccessible::DoCommand() nsITimer::TYPE_ONE_SHOT); } +already_AddRefed +nsAccessible::GetNextWithState(nsIAccessible *aStart, PRUint32 matchState) +{ + // Return the next descendant that matches one of the states in matchState + // Uses depth first search + NS_ASSERTION(matchState, "GetNextWithState() not called with a state to match"); + NS_ASSERTION(aStart, "GetNextWithState() not called with an accessible to start with"); + nsCOMPtr look, current = aStart; + PRUint32 state = 0; + while (0 == (state & matchState)) { + current->GetFirstChild(getter_AddRefs(look)); + while (!look) { + if (current == this) { + return nsnull; // At top of subtree + } + current->GetNextSibling(getter_AddRefs(look)); + if (!look) { + current->GetParent(getter_AddRefs(look)); + current.swap(look); + continue; + } + } + current.swap(look); + current->GetFinalState(&state); + } + + nsIAccessible *returnAccessible = nsnull; + current.swap(returnAccessible); + + return current; +} + +// nsIAccessibleSelectable +NS_IMETHODIMP nsAccessible::GetSelectedChildren(nsIArray **aSelectedAccessibles) +{ + *aSelectedAccessibles = nsnull; + + nsCOMPtr selectedAccessibles; + NS_NewArray(getter_AddRefs(selectedAccessibles)); + if (!selectedAccessibles) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr selected = this; + while ((selected = GetNextWithState(selected, STATE_SELECTED)) != nsnull) { + selectedAccessibles->AppendElement(selected, PR_FALSE); + } + + PRUint32 length = 0; + selectedAccessibles->GetLength(&length); + if (length) { // length of nsIArray containing selected options + *aSelectedAccessibles = selectedAccessibles; + NS_ADDREF(*aSelectedAccessibles); + } + + return NS_OK; +} + +// return the nth selected descendant nsIAccessible object +NS_IMETHODIMP nsAccessible::RefSelection(PRInt32 aIndex, nsIAccessible **aSelected) +{ + *aSelected = nsnull; + if (aIndex < 0) { + return NS_ERROR_FAILURE; + } + nsCOMPtr selected = this; + PRInt32 count = 0; + while (count ++ <= aIndex) { + selected = GetNextWithState(selected, STATE_SELECTED); + if (!selected) { + return NS_ERROR_FAILURE; // aIndex out of range + } + } + NS_IF_ADDREF(*aSelected = selected); + return NS_OK; +} + +NS_IMETHODIMP nsAccessible::GetSelectionCount(PRInt32 *aSelectionCount) +{ + *aSelectionCount = 0; + nsCOMPtr selected = this; + while ((selected = GetNextWithState(selected, STATE_SELECTED)) != nsnull) { + ++ *aSelectionCount; + } + + return NS_OK; +} + +NS_IMETHODIMP nsAccessible::AddChildToSelection(PRInt32 aIndex) +{ + // Tree views and other container widgets which may have grandchildren should + // implement a selection methods for their specific interfaces, because being + // able to deal with selection on a per-child basis would not be enough. + + NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE); + + nsCOMPtr child; + GetChildAt(aIndex, getter_AddRefs(child)); + + PRUint32 state; + nsresult rv = child->GetFinalState(&state); + NS_ENSURE_SUCCESS(rv, rv); + + if (!(state & STATE_SELECTABLE)) { + return NS_OK; + } + + return child->TakeSelection(); +} + +NS_IMETHODIMP nsAccessible::RemoveChildFromSelection(PRInt32 aIndex) +{ + // Tree views and other container widgets which may have grandchildren should + // implement a selection methods for their specific interfaces, because being + // able to deal with selection on a per-child basis would not be enough. + + NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE); + + nsCOMPtr child; + GetChildAt(aIndex, getter_AddRefs(child)); + + PRUint32 state; + nsresult rv = child->GetFinalState(&state); + NS_ENSURE_SUCCESS(rv, rv); + + if (!(state & STATE_SELECTED)) { + return NS_OK; + } + + return child->RemoveSelection(); +} + +NS_IMETHODIMP nsAccessible::IsChildSelected(PRInt32 aIndex, PRBool *aIsSelected) +{ + // Tree views and other container widgets which may have grandchildren should + // implement a selection methods for their specific interfaces, because being + // able to deal with selection on a per-child basis would not be enough. + + *aIsSelected = PR_FALSE; + NS_ENSURE_TRUE(aIndex >= 0, NS_ERROR_FAILURE); + + nsCOMPtr child; + GetChildAt(aIndex, getter_AddRefs(child)); + + PRUint32 state; + nsresult rv = child->GetFinalState(&state); + NS_ENSURE_SUCCESS(rv, rv); + + if (state & STATE_SELECTED) { + *aIsSelected = PR_TRUE; + } + return NS_OK; +} + +NS_IMETHODIMP nsAccessible::ClearSelection() +{ + nsCOMPtr selected = this; + while ((selected = GetNextWithState(selected, STATE_SELECTED)) != nsnull) { + selected->RemoveSelection(); + } + return NS_OK; +} + +NS_IMETHODIMP nsAccessible::SelectAllSelection(PRBool *_retval) +{ + nsCOMPtr selectable = this; + while ((selectable = GetNextWithState(selectable, STATE_SELECTED)) != nsnull) { + selectable->TakeSelection(); + } + return NS_OK; +} + #ifdef MOZ_ACCESSIBILITY_ATK // static helper functions nsresult nsAccessible::GetParentBlockNode(nsIPresShell *aPresShell, nsIDOMNode *aCurrentNode, nsIDOMNode **aBlockNode) diff --git a/mozilla/accessible/src/base/nsAccessible.h b/mozilla/accessible/src/base/nsAccessible.h index 1c1da3106e2..74110fb519d 100644 --- a/mozilla/accessible/src/base/nsAccessible.h +++ b/mozilla/accessible/src/base/nsAccessible.h @@ -43,6 +43,7 @@ #include "nsAccessibilityAtoms.h" #include "nsIAccessible.h" #include "nsPIAccessible.h" +#include "nsIAccessibleSelectable.h" #include "nsIDOMNodeList.h" #include "nsINameSpaceManager.h" #include "nsWeakReference.h" @@ -108,7 +109,8 @@ struct nsRoleMapEntry class nsAccessible : public nsAccessNodeWrap, public nsIAccessible, - public nsPIAccessible + public nsPIAccessible, + public nsIAccessibleSelectable { public: // to eliminate the confusion of "magic numbers" -- if ( 0 ){ foo; } @@ -122,6 +124,7 @@ public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIACCESSIBLE NS_DECL_NSPIACCESSIBLE + NS_DECL_NSIACCESSIBLESELECTABLE // nsIAccessNode NS_IMETHOD Init(); @@ -174,6 +177,11 @@ protected: nsresult AppendFlatStringFromSubtreeRecurse(nsIContent *aContent, nsAString *aFlatString); virtual void CacheChildren(PRBool aWalkAnonContent); + // Selection helpers + already_AddRefed GetNextWithState(nsIAccessible *aStart, PRUint32 matchState); + static already_AddRefed GetMultiSelectFor(nsIDOMNode *aNode); + nsresult SetNonTextSelection(PRBool aSelect); + // For accessibles that have actions static void DoCommandCallback(nsITimer *aTimer, void *aClosure); nsresult DoCommand(); diff --git a/mozilla/accessible/src/base/nsDocAccessible.cpp b/mozilla/accessible/src/base/nsDocAccessible.cpp index ca405efe6b9..6f5c7621da3 100644 --- a/mozilla/accessible/src/base/nsDocAccessible.cpp +++ b/mozilla/accessible/src/base/nsDocAccessible.cpp @@ -753,7 +753,31 @@ nsDocAccessible::AttributeChanged(nsIDocument *aDocument, nsIContent* aContent, } PRUint32 eventType = 0; - if (aNameSpaceID == kNameSpaceID_StatesWAI_Unofficial) { + if (aAttribute == nsAccessibilityAtoms::selected) { + // DHTML or XUL selection + nsCOMPtr multiSelect = GetMultiSelectFor(targetNode); + // 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 + 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 + FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, + multiSelect, nsnull); + nsAutoString attrValue; + aContent->GetAttr(kNameSpaceID_StatesWAI_Unofficial, + nsAccessibilityAtoms::selected, attrValue); + if (attrValue.IsEmpty() || attrValue.EqualsLiteral("false")) { + eventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE; + } + else { + eventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; + } + } + } + else if (aNameSpaceID == kNameSpaceID_StatesWAI_Unofficial) { // DHTML accessibility attributes nsCOMPtr changedContent(do_QueryInterface(targetNode)); if (!changedContent->HasAttr(kNameSpaceID_XHTML2_Unofficial, @@ -775,6 +799,18 @@ nsDocAccessible::AttributeChanged(nsIDocument *aDocument, nsIContent* aContent, else if (aAttribute == nsAccessibilityAtoms::valuenow) { eventType = nsIAccessibleEvent::EVENT_VALUE_CHANGE; } + else if (aAttribute == nsAccessibilityAtoms::multiselect) { + // This affects whether the accessible supports nsIAccessibleSelectable. + // COM says we cannot change what interfaces are supported on-the-fly, + // so invalidate this object. A new one will be created on demand. + nsCOMPtr changedContent(do_QueryInterface(targetNode)); + if (changedContent->HasAttr(kNameSpaceID_XHTML2_Unofficial, + nsAccessibilityAtoms::role)) { + // The multiselect and other waistate attributes only take affect + // when XHTML2:role is present + InvalidateCacheSubtree(changedContent, nsIAccessibleEvent::EVENT_REORDER); + } + } } // Fire after short timer, because we need to wait for diff --git a/mozilla/accessible/src/base/nsRootAccessible.cpp b/mozilla/accessible/src/base/nsRootAccessible.cpp index 041581d7c91..2ede0f41606 100644 --- a/mozilla/accessible/src/base/nsRootAccessible.cpp +++ b/mozilla/accessible/src/base/nsRootAccessible.cpp @@ -231,60 +231,70 @@ nsresult nsRootAccessible::AddEventListeners() if (target) { // capture DOM focus events nsresult rv = target->AddEventListener(NS_LITERAL_STRING("focus"), NS_STATIC_CAST(nsIDOMFocusListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); // capture Form change events rv = target->AddEventListener(NS_LITERAL_STRING("select"), NS_STATIC_CAST(nsIDOMFormListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); + + // capture non-text selection changes + rv = target->AddEventListener(NS_LITERAL_STRING("DOMItemSelected"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + // capture non-text selection changes + rv = target->AddEventListener(NS_LITERAL_STRING("DOMItemUnselected"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); // capture ValueChange events (fired whenever value changes, immediately after, whether focus moves or not) rv = target->AddEventListener(NS_LITERAL_STRING("ValueChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); // capture AlertActive events (fired whenever alert pops up) rv = target->AddEventListener(NS_LITERAL_STRING("AlertActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); // add ourself as a OpenStateChange listener (custom event fired in tree.xml) rv = target->AddEventListener(NS_LITERAL_STRING("OpenStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); // add ourself as a CheckboxStateChange listener (custom event fired in nsHTMLInputElement.cpp) rv = target->AddEventListener(NS_LITERAL_STRING("CheckboxStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); // add ourself as a RadioStateChange Listener ( custom event fired in in nsHTMLInputElement.cpp & radio.xml) rv = target->AddEventListener(NS_LITERAL_STRING("RadioStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("popupshown"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("popuphiding"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuInactive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuItemActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuBarActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuBarInactive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); - NS_ASSERTION(NS_SUCCEEDED(rv), "failed to register listener"); + NS_ENSURE_SUCCESS(rv, rv); } GetChromeEventHandler(getter_AddRefs(target)); NS_ASSERTION(target, "No chrome event handler for document"); if (target) { - target->AddEventListener(NS_LITERAL_STRING("PageHide"), - NS_STATIC_CAST(nsIDOMXULListener*, this), - PR_TRUE); + nsresult rv = target->AddEventListener(NS_LITERAL_STRING("PageHide"), + NS_STATIC_CAST(nsIDOMXULListener*, this), + PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); target->AddEventListener(NS_LITERAL_STRING("PageShow"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); } if (!mCaretAccessible) @@ -307,6 +317,8 @@ nsresult nsRootAccessible::RemoveEventListeners() if (target) { target->RemoveEventListener(NS_LITERAL_STRING("focus"), NS_STATIC_CAST(nsIDOMFocusListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("select"), NS_STATIC_CAST(nsIDOMFormListener*, this), PR_TRUE); + target->RemoveEventListener(NS_LITERAL_STRING("DOMItemSelected"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); + target->RemoveEventListener(NS_LITERAL_STRING("DOMItemUnselected"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("ValueChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("AlertActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("OpenStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); @@ -370,16 +382,28 @@ void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible, // Special dynamic content handling PRUint32 naturalRole; // The natural role is the role that this type of element normally has aAccessible->GetRole(&naturalRole); - if (role == ROLE_MENUITEM) { - if (role != naturalRole && !mIsInDHTMLMenu) { // Entering menus - privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUSTART, - this, nsnull); + if (role != naturalRole) { + nsCOMPtr multiSelect = GetMultiSelectFor(aNode); + if (!multiSelect) { + // Selection events that mirror focus events + // Mirror selection events to focus, but only for widgets that are selectable + // but not a descendent of a multi-selectable widget + // Selection events for multiselects is handled separately + // in nsDocAccessible::AttributeChanged() for DHTML widgets and + // in nsRootAccessible::HandleEvent() via + // DOMItemSelected and DOMItemUnselected for everything else + FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION, + aAccessible, nsnull); + } + if (role == ROLE_MENUITEM) { + if (!mIsInDHTMLMenu) { // Entering menus + FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUSTART, this, nsnull); + } mIsInDHTMLMenu = PR_TRUE; } } - else if (mIsInDHTMLMenu) { // Leaving menus - privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUEND, - this, nsnull); + if (role != ROLE_MENUITEM && mIsInDHTMLMenu) { // Leaving menus + FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUEND, this, nsnull); mIsInDHTMLMenu = PR_FALSE; } @@ -654,6 +678,20 @@ NS_IMETHODIMP nsRootAccessible::HandleEvent(nsIDOMEvent* aEvent) // Focus was inside of popup that's being hidden FireCurrentFocusEvent(); } + else if (eventType.EqualsLiteral("DOMItemUnselected")) { + nsCOMPtr multiSelect = GetMultiSelectFor(targetNode); + if (multiSelect) { + privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, multiSelect, nsnull); + privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE, accessible, nsnull); + } + } + else if (eventType.EqualsLiteral("DOMItemSelected")) { + nsCOMPtr multiSelect = GetMultiSelectFor(targetNode); + if (multiSelect) { + privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, multiSelect, nsnull); + privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD, accessible, nsnull); + } + } else { // Menu popup events PRUint32 menuEvent = 0; diff --git a/mozilla/accessible/src/html/nsHTMLSelectAccessible.h b/mozilla/accessible/src/html/nsHTMLSelectAccessible.h index 3287437ff52..31adba9479a 100644 --- a/mozilla/accessible/src/html/nsHTMLSelectAccessible.h +++ b/mozilla/accessible/src/html/nsHTMLSelectAccessible.h @@ -71,8 +71,7 @@ /* * The HTML implementation of nsIAccessibleSelectable. */ -class nsHTMLSelectableAccessible : public nsAccessibleWrap, - public nsIAccessibleSelectable +class nsHTMLSelectableAccessible : public nsAccessibleWrap { public: diff --git a/mozilla/accessible/src/xul/nsXULSelectAccessible.h b/mozilla/accessible/src/xul/nsXULSelectAccessible.h index 3ae8b85a89c..39028abb497 100644 --- a/mozilla/accessible/src/xul/nsXULSelectAccessible.h +++ b/mozilla/accessible/src/xul/nsXULSelectAccessible.h @@ -69,8 +69,7 @@ class nsIWeakReference; /* * The basic implemetation of nsIAccessibleSelectable. */ -class nsXULSelectableAccessible : public nsAccessibleWrap, - public nsIAccessibleSelectable +class nsXULSelectableAccessible : public nsAccessibleWrap { public: NS_DECL_ISUPPORTS_INHERITED diff --git a/mozilla/content/html/content/src/nsHTMLOptionElement.cpp b/mozilla/content/html/content/src/nsHTMLOptionElement.cpp index 795ba6934f2..f26466e1f6e 100644 --- a/mozilla/content/html/content/src/nsHTMLOptionElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLOptionElement.cpp @@ -68,6 +68,9 @@ #include "nsLayoutAtoms.h" #include "nsIEventStateManager.h" #include "nsIDOMDocument.h" +#include "nsIDOMDocumentEvent.h" +#include "nsIDOMEvent.h" +#include "nsIPrivateDOMEvent.h" /** * Implementation of <option> @@ -128,6 +131,11 @@ protected: */ void GetSelect(nsIDOMHTMLSelectElement **aSelectElement) const; + /** + * Fire a DOM event for this option node + */ + void DispatchDOMEvent(const nsAString& aName); + PRPackedBool mIsInitialized; PRPackedBool mIsSelected; }; @@ -205,6 +213,31 @@ nsHTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm) return NS_OK; } +void +nsHTMLOptionElement::DispatchDOMEvent(const nsAString& aName) +{ + nsCOMPtr domDoc = do_QueryInterface(GetOwnerDoc()); + if (domDoc) { + nsCOMPtr selectEvent; + domDoc->CreateEvent(NS_LITERAL_STRING("Events"), + getter_AddRefs(selectEvent)); + + nsCOMPtr privateEvent(do_QueryInterface(selectEvent)); + + if (privateEvent) { + nsresult rv = selectEvent->InitEvent(aName, PR_TRUE, PR_TRUE); + if (NS_SUCCEEDED(rv)) { + privateEvent->SetTrusted(PR_TRUE); + + nsCOMPtr target = + do_QueryInterface(NS_STATIC_CAST(nsIDOMNode*, this)); + PRBool defaultActionEnabled; + target->DispatchEvent(selectEvent, &defaultActionEnabled); + } + } + } +} + NS_IMETHODIMP nsHTMLOptionElement::SetSelectedInternal(PRBool aValue, PRBool aNotify) { @@ -219,6 +252,14 @@ nsHTMLOptionElement::SetSelectedInternal(PRBool aValue, PRBool aNotify) } } + // Dispatch an event to notify the subcontent that the selected item has changed + if (aValue) { + DispatchDOMEvent(NS_LITERAL_STRING("DOMItemSelected")); + } + else { + DispatchDOMEvent(NS_LITERAL_STRING("DOMItemUnselected")); + } + return NS_OK; } diff --git a/mozilla/layout/forms/nsListControlFrame.cpp b/mozilla/layout/forms/nsListControlFrame.cpp index e58e0d5677a..9b13f3b504e 100644 --- a/mozilla/layout/forms/nsListControlFrame.cpp +++ b/mozilla/layout/forms/nsListControlFrame.cpp @@ -1331,10 +1331,6 @@ nsListControlFrame::PerformSelection(PRInt32 aClickedIndex, wasChanged = SingleSelection(aClickedIndex, PR_FALSE); } -#ifdef ACCESSIBILITY - FireMenuItemActiveEvent(); // Inform assistive tech what got focus -#endif - return wasChanged; } @@ -2189,6 +2185,10 @@ nsListControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex, PRInt32 aNewIndex) mStartSelectionIndex = aNewIndex; mEndSelectionIndex = aNewIndex; +#ifdef ACCESSIBILITY + FireMenuItemActiveEvent(); +#endif + return NS_OK; } @@ -2558,8 +2558,18 @@ PRBool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent) void nsListControlFrame::FireMenuItemActiveEvent() { + if (mFocused != this && !IsInDropDownMode()) { + return; + } + + // The mEndSelectionIndex is what is currently being selected + // use the selected index if this is kNothingSelected PRInt32 focusedIndex; - GetSelectedIndex(&focusedIndex); + if (mEndSelectionIndex == kNothingSelected) { + GetSelectedIndex(&focusedIndex); + } else { + focusedIndex = mEndSelectionIndex; + } if (focusedIndex == kNothingSelected) { return; } @@ -2708,6 +2718,9 @@ nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent) // Handle Like List CaptureMouseEvents(GetPresContext(), PR_TRUE); mChangesSinceDragStart = HandleListSelection(aMouseEvent, selectedIndex); + if (mChangesSinceDragStart) { + UpdateSelection(); // dispatch event, update combobox, etc. + } } else { // NOTE: the combo box is responsible for dropping it down if (mComboboxFrame) { @@ -3075,7 +3088,11 @@ nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent) keycode == nsIDOMKeyEvent::DOM_VK_LEFT || keycode == nsIDOMKeyEvent::DOM_VK_DOWN || keycode == nsIDOMKeyEvent::DOM_VK_RIGHT)) { - mControlSelectMode = PR_TRUE; + PRBool isMultiple; + GetMultiple(&isMultiple); + // Don't go into multiple select mode unless this list can handle it + mControlSelectMode = isMultiple; + isControl = isMultiple; } else if (charcode != ' ') { mControlSelectMode = PR_FALSE; } @@ -3254,18 +3271,24 @@ nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent) // do the scrolling for us if (newIndex != kNothingSelected) { // If you hold control, no key will actually do anything except space. + PRBool wasChanged = PR_FALSE; if (isControl && charcode != ' ') { mStartSelectionIndex = newIndex; mEndSelectionIndex = newIndex; ScrollToIndex(newIndex); } else if (mControlSelectMode && charcode == ' ') { - SingleSelection(newIndex, PR_TRUE); + wasChanged = SingleSelection(newIndex, PR_TRUE); } else { - PRBool wasChanged = PerformSelection(newIndex, isShift, isControl); - if (wasChanged) { - UpdateSelection(); // dispatch event, update combobox, etc. - } + wasChanged = PerformSelection(newIndex, isShift, isControl); } + if (wasChanged) { + UpdateSelection(); // dispatch event, update combobox, etc. + } +#ifdef ACCESSIBILITY + if (charcode != ' ') { + FireMenuItemActiveEvent(); + } +#endif // XXX - Are we cover up a problem here??? // Why aren't they getting flushed each time?