/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** 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 * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * 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 ***** */ // NOTE: alphabetically ordered #include "nsAccessibilityService.h" #include "nsAccessibleEventData.h" #include "nsCaretAccessible.h" #include "nsHTMLSelectAccessible.h" #include "nsIAccessibleCaret.h" #include "nsIChromeEventHandler.h" #include "nsIDOMElement.h" #include "nsIDOMEventListener.h" #include "nsIDOMEventTarget.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsIDOMHTMLImageElement.h" #include "nsIDOMHTMLInputElement.h" #include "nsIDOMHTMLSelectElement.h" #include "nsIDOMNSEvent.h" #include "nsIDOMWindow.h" #include "nsIDOMXULMenuListElement.h" #include "nsIDOMXULMultSelectCntrlEl.h" #include "nsIDOMXULSelectCntrlItemEl.h" #include "nsIDOMXULPopupElement.h" #include "nsIDocument.h" #include "nsIEventListenerManager.h" #include "nsIHTMLDocument.h" #include "nsIFocusController.h" #include "nsIFrame.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIScriptGlobalObject.h" #include "nsIScrollableView.h" #include "nsIServiceManager.h" #include "nsIViewManager.h" #include "nsLayoutAtoms.h" #include "nsPIDOMWindow.h" #include "nsReadableUtils.h" #include "nsRootAccessible.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeNode.h" #include "nsIDocShellTreeOwner.h" #include "nsIBaseWindow.h" #ifdef MOZ_XUL #include "nsXULTreeAccessible.h" #include "nsIXULDocument.h" #endif #include "nsAccessibilityService.h" #include "nsISelectionPrivate.h" #include "nsICaret.h" #include "nsIDOMHTMLInputElement.h" #include "nsAccessibleEventData.h" #include "nsIDOMDocument.h" #ifdef MOZ_ACCESSIBILITY_ATK #include "nsIAccessibleHyperText.h" #endif NS_INTERFACE_MAP_BEGIN(nsRootAccessible) NS_INTERFACE_MAP_ENTRY(nsIDOMFocusListener) NS_INTERFACE_MAP_ENTRY(nsIDOMFormListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFormListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMFormListener) NS_INTERFACE_MAP_END_INHERITING(nsDocAccessible) NS_IMPL_ADDREF_INHERITED(nsRootAccessible, nsDocAccessible) NS_IMPL_RELEASE_INHERITED(nsRootAccessible, nsDocAccessible) //----------------------------------------------------- // construction //----------------------------------------------------- nsRootAccessible::nsRootAccessible(nsIDOMNode *aDOMNode, nsIWeakReference* aShell): nsDocAccessibleWrap(aDOMNode, aShell), mAccService(do_GetService("@mozilla.org/accessibilityService;1")), mIsInDHTMLMenu(PR_FALSE) { } //----------------------------------------------------- // destruction //----------------------------------------------------- nsRootAccessible::~nsRootAccessible() { } // helpers /* readonly attribute AString name; */ NS_IMETHODIMP nsRootAccessible::GetName(nsAString& aName) { if (!mDocument) { return NS_ERROR_FAILURE; } if (mRoleMapEntry) { nsAccessible::GetName(aName); if (!aName.IsEmpty()) { return NS_OK; } } nsIScriptGlobalObject *globalScript = mDocument->GetScriptGlobalObject(); nsIDocShell *docShell = nsnull; if (globalScript) { docShell = globalScript->GetDocShell(); } nsCOMPtr docShellAsItem(do_QueryInterface(docShell)); if(!docShellAsItem) return NS_ERROR_FAILURE; nsCOMPtr treeOwner; docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner)); nsCOMPtr baseWindow(do_QueryInterface(treeOwner)); if (baseWindow) { nsXPIDLString title; baseWindow->GetTitle(getter_Copies(title)); aName.Assign(title); return NS_OK; } return NS_ERROR_FAILURE; } /* readonly attribute nsIAccessible accParent; */ NS_IMETHODIMP nsRootAccessible::GetParent(nsIAccessible * *aParent) { *aParent = nsnull; return NS_OK; } /* readonly attribute unsigned long accRole; */ NS_IMETHODIMP nsRootAccessible::GetRole(PRUint32 *aRole) { if (!mDocument) { return NS_ERROR_FAILURE; } // If it's a or , use ROLE_DIALOG instead nsIContent *rootContent = mDocument->GetRootContent(); if (rootContent) { nsCOMPtr rootElement(do_QueryInterface(rootContent)); if (rootElement) { nsAutoString name; rootElement->GetLocalName(name); if (name.EqualsLiteral("dialog") || name.EqualsLiteral("wizard")) { *aRole = ROLE_DIALOG; // Always at the root return NS_OK; } } } return nsDocAccessibleWrap::GetRole(aRole); } NS_IMETHODIMP nsRootAccessible::GetState(PRUint32 *aState) { nsresult rv = NS_ERROR_FAILURE; if (mDOMNode) { rv = nsDocAccessibleWrap::GetState(aState); } if (NS_FAILED(rv)) { return rv; } NS_ASSERTION(mDocument, "mDocument should not be null unless mDOMNode is"); if (gLastFocusedNode) { nsCOMPtr rootAccessibleDoc(do_QueryInterface(mDocument)); nsCOMPtr focusedDoc; gLastFocusedNode->GetOwnerDocument(getter_AddRefs(focusedDoc)); if (rootAccessibleDoc == focusedDoc) { *aState |= STATE_FOCUSED; } } return NS_OK; } void nsRootAccessible::GetChromeEventHandler(nsIDOMEventTarget **aChromeTarget) { nsCOMPtr domWin; GetWindow(getter_AddRefs(domWin)); nsCOMPtr privateDOMWindow(do_QueryInterface(domWin)); nsCOMPtr chromeEventHandler; if (privateDOMWindow) { chromeEventHandler = privateDOMWindow->GetChromeEventHandler(); } nsCOMPtr target(do_QueryInterface(chromeEventHandler)); *aChromeTarget = target; NS_IF_ADDREF(*aChromeTarget); } nsresult nsRootAccessible::AddEventListeners() { // use AddEventListener from the nsIDOMEventTarget interface nsCOMPtr target(do_QueryInterface(mDocument)); if (target) { // capture DOM focus events nsresult rv = target->AddEventListener(NS_LITERAL_STRING("focus"), NS_STATIC_CAST(nsIDOMFocusListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); // capture Form change events rv = target->AddEventListener(NS_LITERAL_STRING("select"), NS_STATIC_CAST(nsIDOMFormListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); // capture NameChange events (fired whenever name changes, immediately after, whether focus moves or not) rv = target->AddEventListener(NS_LITERAL_STRING("NameChange"), 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_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_ENSURE_SUCCESS(rv, rv); // add ourself as a TreeViewChanged listener (custom event fired in tree.xml) rv = target->AddEventListener(NS_LITERAL_STRING("TreeViewChanged"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); 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_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_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_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("popupshown"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("popuphiding"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuInactive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuItemActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuBarActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMMenuBarInactive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); rv = target->AddEventListener(NS_LITERAL_STRING("DOMContentLoaded"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); } GetChromeEventHandler(getter_AddRefs(target)); NS_ASSERTION(target, "No chrome event handler for document"); if (target) { nsresult rv = target->AddEventListener(NS_LITERAL_STRING("pagehide"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); } if (!mCaretAccessible) mCaretAccessible = new nsCaretAccessible(mDOMNode, mWeakShell, this); // Fire accessible focus event for pre-existing focus, but wait until all internal // focus events are finished for window initialization. mFireFocusTimer = do_CreateInstance("@mozilla.org/timer;1"); if (mFireFocusTimer) { mFireFocusTimer->InitWithFuncCallback(FireFocusCallback, this, 0, nsITimer::TYPE_ONE_SHOT); } return nsDocAccessible::AddEventListeners(); } nsresult nsRootAccessible::RemoveEventListeners() { nsCOMPtr target(do_QueryInterface(mDocument)); 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("NameChange"), 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("TreeViewChanged"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("OpenStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("CheckboxStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("RadioStateChange"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("popupshown"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("popuphiding"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("DOMMenuInactive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("DOMMenuItemActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("DOMMenuBarActive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("DOMMenuBarInactive"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); target->RemoveEventListener(NS_LITERAL_STRING("DOMContentLoaded"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); } GetChromeEventHandler(getter_AddRefs(target)); if (target) { target->RemoveEventListener(NS_LITERAL_STRING("pagehide"), NS_STATIC_CAST(nsIDOMXULListener*, this), PR_TRUE); } if (mCaretAccessible) { mCaretAccessible->RemoveSelectionListener(); mCaretAccessible = nsnull; } mAccService = nsnull; return nsDocAccessible::RemoveEventListeners(); } NS_IMETHODIMP nsRootAccessible::GetCaretAccessible(nsIAccessible **aCaretAccessible) { *aCaretAccessible = nsnull; if (mCaretAccessible) { CallQueryInterface(mCaretAccessible, aCaretAccessible); } return NS_OK; } void nsRootAccessible::TryFireEarlyLoadEvent(nsIAccessible *aAccessible, nsIDOMNode *aDocNode) { // We can fire an early load event based on DOMContentLoaded unless we // have subdocuments. For that we wait until WebProgressListener // STATE_STOP handling in nsAccessibilityService. // Note, we don't fire any page load finished events for chrome or for // frame/iframe page loads during the initial complete page load -- that page // load event for the entire content pane needs to stand alone. // This also works for firing events for error pages nsCOMPtr treeItem = GetDocShellTreeItemFor(aDocNode); NS_ASSERTION(treeItem, "No docshelltreeitem for aDocNode"); if (!treeItem) { return; } PRInt32 itemType; treeItem->GetItemType(&itemType); if (itemType != nsIDocShellTreeItem::typeContent) { return; } nsCOMPtr treeNode(do_QueryInterface(treeItem)); if (treeNode) { PRInt32 subDocuments; treeNode->GetChildCount(&subDocuments); if (subDocuments) { return; } } nsCOMPtr rootContentTreeItem; treeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem)); NS_ASSERTION(rootContentTreeItem, "No root content tree item"); if (!rootContentTreeItem) { // Not at root of content return; } if (rootContentTreeItem != treeItem) { nsCOMPtr rootContentDocAccessible = GetDocAccessibleFor(rootContentTreeItem); nsCOMPtr rootContentAccessible = do_QueryInterface(rootContentDocAccessible); if (!rootContentAccessible) { return; } PRUint32 state; rootContentAccessible->GetFinalState(&state); if (state & STATE_BUSY) { // Don't fire page load events on subdocuments for initial page load of entire page return; } } // No frames or iframes, so we can fire the doc load finished event early nsCOMPtr docAccessible = do_QueryInterface(aAccessible); NS_ASSERTION(docAccessible, "No doc aAccessible for DOMContentLoaded"); if (docAccessible) { docAccessible->FireDocLoadingEvent(PR_TRUE); } } void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible, nsIDOMNode *aNode, nsIDOMEvent *aFocusEvent, PRBool aForceEvent) { NS_ASSERTION(aAccessible, "Attempted to fire focus event for no accessible"); if (mCaretAccessible) { nsCOMPtr nsevent(do_QueryInterface(aFocusEvent)); if (nsevent) { // Use the originally focused node where the selection lives. // 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. nsCOMPtr domEventTarget; nsevent->GetOriginalTarget(getter_AddRefs(domEventTarget)); nsCOMPtr realFocusedNode = do_QueryInterface(domEventTarget); mCaretAccessible->AttachNewSelectionListener(realFocusedNode); } } // Fire focus only if it changes, but always fire focus events when aForceEvent == PR_TRUE if (gLastFocusedNode == aNode && !aForceEvent) { return; } nsCOMPtr privateAccessible = do_QueryInterface(aAccessible); NS_ASSERTION(privateAccessible , "No nsPIAccessible for nsIAccessible"); // Use focus events on DHTML menuitems to indicate when to fire menustart and menuend // Special dynamic content handling PRUint32 role = ROLE_NOTHING; aAccessible->GetFinalRole(&role); if (role == ROLE_MENUITEM) { if (!mIsInDHTMLMenu) { // Entering menus PRUint32 naturalRole; // The natural role is the role that this type of element normally has aAccessible->GetRole(&naturalRole); if (role != naturalRole) { // Must be a DHTML menuitem FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUSTART, this, nsnull); mIsInDHTMLMenu = ROLE_MENUITEM; } } } else if (mIsInDHTMLMenu) { FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUEND, this, nsnull); mIsInDHTMLMenu = PR_FALSE; } NS_IF_RELEASE(gLastFocusedNode); gLastFocusedNode = aNode; NS_IF_ADDREF(gLastFocusedNode); privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, aAccessible, nsnull); } void nsRootAccessible::FireCurrentFocusEvent() { nsCOMPtr domWin; GetWindow(getter_AddRefs(domWin)); nsCOMPtr privateDOMWindow(do_QueryInterface(domWin)); if (!privateDOMWindow) { return; } nsIFocusController *focusController = privateDOMWindow->GetRootFocusController(); if (!focusController) { return; } nsCOMPtr focusedElement; focusController->GetFocusedElement(getter_AddRefs(focusedElement)); nsCOMPtr focusedNode(do_QueryInterface(focusedElement)); if (!focusedNode) { // Document itself may have focus nsCOMPtr focusedWinInternal; focusController->GetFocusedWindow(getter_AddRefs(focusedWinInternal)); if (focusedWinInternal) { nsCOMPtr focusedDOMDocument; focusedWinInternal->GetDocument(getter_AddRefs(focusedDOMDocument)); focusedNode = do_QueryInterface(focusedDOMDocument); } if (!focusedNode) { return; // Could not get a focused document either } } // Simulate a focus event so that we can reuse code that fires focus for container children like treeitems nsIContent *rootContent = mDocument->GetRootContent(); nsPresContext *presContext = GetPresContext(); if (rootContent && presContext) { nsCOMPtr event; nsCOMPtr manager; rootContent->GetListenerManager(getter_AddRefs(manager)); if (manager && NS_SUCCEEDED(manager->CreateEvent(presContext, nsnull, NS_LITERAL_STRING("Events"), getter_AddRefs(event))) && NS_SUCCEEDED(event->InitEvent(NS_LITERAL_STRING("focus"), PR_TRUE, PR_TRUE))) { HandleEvent(event); } } } // --------------- nsIDOMEventListener Methods (3) ------------------------ NS_IMETHODIMP nsRootAccessible::HandleEvent(nsIDOMEvent* aEvent) { // Turn DOM events in accessibility events // Get info about event and target nsCOMPtr targetNode; GetTargetNode(aEvent, getter_AddRefs(targetNode)); if (!targetNode) return NS_ERROR_FAILURE; nsAutoString eventType; aEvent->GetType(eventType); nsAutoString localName; targetNode->GetLocalName(localName); #ifdef DEBUG_aleventhal // Very useful for debugging, please leave this here. if (eventType.LowerCaseEqualsLiteral("popupshown")) { printf("\ndebugging dommenuitemactive events for %s", NS_ConvertUCS2toUTF8(localName).get()); } if (localName.EqualsIgnoreCase("popup")) { printf("\ndebugging events in popup, event is %s", NS_ConvertUCS2toUTF8(eventType).get()); } if (localName.EqualsIgnoreCase("select")) { printf("\ndebugging events in select, event is %s", NS_ConvertUCS2toUTF8(eventType).get()); } #endif nsCOMPtr eventShell = GetPresShellFor(targetNode); #ifdef MOZ_ACCESSIBILITY_ATK nsCOMPtr anchorElement(do_QueryInterface(targetNode)); // For ATK, check whether this link is for an image. // If so, fire event for the image. if (anchorElement) { nsCOMPtr childNode; if (NS_SUCCEEDED(targetNode->GetFirstChild(getter_AddRefs(childNode)))) { while (childNode) { nsCOMPtr imgNode(do_QueryInterface(childNode)); if (imgNode) { anchorElement = nsnull; // ignore the link targetNode = childNode; // only fire event for image break; } nsCOMPtr tmpNode; tmpNode.swap(childNode); tmpNode->GetNextSibling(getter_AddRefs(childNode)); } } } if (anchorElement) { nsCOMPtr blockNode; // For ATK, we don't create any individual object for hyperlink, use its parent who has block frame instead if (NS_SUCCEEDED(nsAccessible::GetParentBlockNode(eventShell, targetNode, getter_AddRefs(blockNode)))) targetNode = blockNode; } #endif if (!eventShell) { return NS_OK; } if (eventType.LowerCaseEqualsLiteral("pagehide")) { // Only get cached accessible for pagehide -- so that we don't create it // just to destroy it. nsCOMPtr weakShell(do_GetWeakReference(eventShell)); nsCOMPtr accessibleDoc = nsAccessNode::GetDocAccessibleFor(weakShell); nsCOMPtr privateAccDoc = do_QueryInterface(accessibleDoc); if (privateAccDoc) { privateAccDoc->Destroy(); } return NS_OK; } if (eventType.LowerCaseEqualsLiteral("popupshown")) { // Fire menupopupstart events after a delay so that ancestor views // are visible, otherwise an accessible cannot be created for the // popup and the accessibility toolkit event can't be fired. nsCOMPtr popup(do_QueryInterface(targetNode)); if (popup) { return FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUPSTART, targetNode, nsnull); } } if (eventType.EqualsLiteral("TreeViewChanged")) { NS_ENSURE_TRUE(localName.EqualsLiteral("tree"), NS_OK); nsCOMPtr treeContent = do_QueryInterface(targetNode); return mAccService->InvalidateSubtreeFor(eventShell, treeContent, nsIAccessibleEvent::EVENT_REORDER); } nsCOMPtr accessible; if (NS_FAILED(mAccService->GetAccessibleInShell(targetNode, eventShell, getter_AddRefs(accessible)))) return NS_OK; #ifdef MOZ_XUL // If it's a tree element, need the currently selected item nsCOMPtr treeItemAccessible; if (localName.EqualsLiteral("tree")) { nsCOMPtr multiSelect = do_QueryInterface(targetNode); if (multiSelect) { PRInt32 treeIndex = -1; multiSelect->GetCurrentIndex(&treeIndex); if (treeIndex >= 0) { nsCOMPtr treeCache(do_QueryInterface(accessible)); if (!treeCache || NS_FAILED(treeCache->GetCachedTreeitemAccessible( treeIndex, nsnull, getter_AddRefs(treeItemAccessible))) || !treeItemAccessible) { return NS_ERROR_OUT_OF_MEMORY; } accessible = treeItemAccessible; } } } #endif nsCOMPtr privAcc(do_QueryInterface(accessible)); #ifndef MOZ_ACCESSIBILITY_ATK #ifdef MOZ_XUL // tree event if (eventType.LowerCaseEqualsLiteral("checkboxstatechange") || eventType.LowerCaseEqualsLiteral("openstatechange")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, accessible, nsnull); return NS_OK; } else if (treeItemAccessible) { if (eventType.LowerCaseEqualsLiteral("focus")) { FireAccessibleFocusEvent(accessible, targetNode, aEvent); // Tree has focus } else if (eventType.LowerCaseEqualsLiteral("dommenuitemactive")) { FireAccessibleFocusEvent(treeItemAccessible, targetNode, aEvent, PR_TRUE); } else if (eventType.LowerCaseEqualsLiteral("namechange")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, accessible, nsnull); } else if (eventType.LowerCaseEqualsLiteral("select")) { // If multiselect tree, we should fire selectionadd or selection removed if (gLastFocusedNode == targetNode) { nsCOMPtr multiSel = do_QueryInterface(targetNode); nsAutoString selType; multiSel->GetSelType(selType); if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, accessible, nsnull); // XXX We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE // for each tree item. Perhaps each tree item will need to // cache its selection state and fire an event after a DOM "select" // event when that state changes. // nsXULTreeAccessible::UpdateTreeSelection(); } else { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION, treeItemAccessible, nsnull); } } } return NS_OK; } else #endif if (eventType.LowerCaseEqualsLiteral("dommenuitemactive")) { nsCOMPtr containerAccessible = accessible; PRUint32 containerState = 0; do { nsIAccessible *tempAccessible = containerAccessible; tempAccessible->GetParent(getter_AddRefs(containerAccessible)); if (!containerAccessible) { break; } containerAccessible->GetFinalState(&containerState); } while ((containerState & STATE_HASPOPUP) == 0); // Only fire focus event for DOMMenuItemActive is not inside collapsed popup if (0 == (containerState & STATE_COLLAPSED)) { FireAccessibleFocusEvent(accessible, targetNode, aEvent, PR_TRUE); } } else if (eventType.LowerCaseEqualsLiteral("focus")) { nsCOMPtr selectControl = do_QueryInterface(targetNode); // Send focus to individual radio button or selected item if (selectControl) { nsCOMPtr 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 selectedItem; selectControl->GetSelectedItem(getter_AddRefs(selectedItem)); if (selectedItem) { targetNode = do_QueryInterface(selectedItem); } if (!targetNode || NS_FAILED(mAccService->GetAccessibleInShell(targetNode, eventShell, getter_AddRefs(accessible)))) { return NS_OK; } } } if (accessible == this) { // Top level window focus events already automatically fired by MSAA // based on HWND activities. Don't fire the extra focus event. NS_IF_RELEASE(gLastFocusedNode); gLastFocusedNode = mDOMNode; NS_IF_ADDREF(gLastFocusedNode); return NS_OK; } FireAccessibleFocusEvent(accessible, targetNode, aEvent); } else if (eventType.LowerCaseEqualsLiteral("namechange")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, accessible, nsnull); } else if (eventType.EqualsLiteral("ValueChange")) { PRUint32 role; accessible->GetFinalRole(&role); if (role == ROLE_PROGRESSBAR) { // For progressmeter, fire EVENT_SHOW on 1st value change nsAutoString value; accessible->GetFinalValue(value); if (value.EqualsLiteral("0%")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SHOW, accessible, nsnull); } } privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, accessible, nsnull); } else if (eventType.EqualsLiteral("AlertActive")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, accessible, nsnull); } else if (eventType.LowerCaseEqualsLiteral("radiostatechange") ) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, accessible, nsnull); PRUint32 finalState; accessible->GetFinalState(&finalState); if (finalState & (STATE_CHECKED | STATE_SELECTED)) { FireAccessibleFocusEvent(accessible, targetNode, aEvent); } } else if (eventType.LowerCaseEqualsLiteral("dommenubaractive")) privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUSTART, accessible, nsnull); else if (eventType.LowerCaseEqualsLiteral("dommenubarinactive")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUEND, accessible, nsnull); FireCurrentFocusEvent(); } else if (eventType.LowerCaseEqualsLiteral("popuphiding")) { // If accessible focus was 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. if (!gLastFocusedNode) { return NS_OK; } nsCOMPtr parentOfFocus; gLastFocusedNode->GetParentNode(getter_AddRefs(parentOfFocus)); if (parentOfFocus != targetNode) { return NS_OK; } // Focus was inside of popup that's being hidden FireCurrentFocusEvent(); } else if (eventType.LowerCaseEqualsLiteral("domcontentloaded")) { // Don't create the doc accessible until load scripts have a chance to set // role attribute for or element, because the value of // role attribute will be cached when the doc accessible is Init()'d TryFireEarlyLoadEvent(accessible, targetNode); } else if (eventType.EqualsLiteral("DOMMenuInactive")) { nsCOMPtr popup(do_QueryInterface(targetNode)); if (popup) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUPEND, accessible, nsnull); } } #else AtkStateChange stateData; if (eventType.LowerCaseEqualsLiteral("focus") || eventType.LowerCaseEqualsLiteral("dommenuitemactive")) { if (treeItemAccessible) { // use focused treeitem privAcc = do_QueryInterface(treeItemAccessible); privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, treeItemAccessible, nsnull); } else if (anchorElement) { nsCOMPtr hyperText(do_QueryInterface(accessible)); if (hyperText) { nsCOMPtr focusedNode(do_QueryInterface(anchorElement)); NS_IF_RELEASE(gLastFocusedNode); gLastFocusedNode = focusedNode; NS_IF_ADDREF(gLastFocusedNode); PRInt32 selectedLink; hyperText->GetSelectedLinkIndex(&selectedLink); privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_ATK_LINK_SELECTED, accessible, &selectedLink); } } else if (localName.EqualsIgnoreCase("radiogroup")) { // fire focus event for checked radio instead of radiogroup PRInt32 childCount = 0; accessible->GetChildCount(&childCount); nsCOMPtr radioAcc; for (PRInt32 index = 0; index < childCount; index++) { accessible->GetChildAt(index, getter_AddRefs(radioAcc)); if (radioAcc) { radioAcc->GetFinalState(&stateData.state); if (stateData.state & (STATE_CHECKED | STATE_SELECTED)) { break; } } } if (radioAcc) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, radioAcc, nsnull); } } else FireAccessibleFocusEvent(accessible, targetNode, aEvent); } else if (eventType.LowerCaseEqualsLiteral("select")) { if (treeItemAccessible) { // it's a XUL // use EVENT_FOCUS instead of EVENT_ATK_SELECTION_CHANGE privAcc = do_QueryInterface(treeItemAccessible); privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, treeItemAccessible, nsnull); } else if (localName.LowerCaseEqualsLiteral("tabpanels")) { // make GOK refresh "UI-Grab" window privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_REORDER, accessible, nsnull); } } #if 0 // XXX todo: value change events for ATK are done with // AtkPropertyChange, PROP_VALUE. Need the old and new value. // Not sure how we'll get the old value. // Aaron: I think this is a problem with the ATK API -- its much harder to // grab the old value for all the application developers than it is for // AT's to cache old values when they need to (when would that be!?) else if (eventType.LowerCaseEqualsLiteral("valuechange")) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, accessible, nsnull); } #endif else if (eventType.LowerCaseEqualsLiteral("checkboxstatechange") || // it's a XUL eventType.LowerCaseEqualsLiteral("radiostatechange")) { // it's a XUL accessible->GetFinalState(&stateData.state); // prefPane tab is implemented as list items in A11y, so we need to check STATE_SELECTED also stateData.enable = (stateData.state & (STATE_CHECKED | STATE_SELECTED)) != 0; stateData.state = STATE_CHECKED; privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, accessible, &stateData); // only fire focus event for checked radio if (eventType.LowerCaseEqualsLiteral("radiostatechange") && stateData.enable) { FireAccessibleFocusEvent(accessible, targetNode, aEvent); } } else if (eventType.LowerCaseEqualsLiteral("openstatechange")) { // collapsed/expanded changed accessible->GetFinalState(&stateData.state); stateData.enable = (stateData.state & STATE_EXPANDED) != 0; stateData.state = STATE_EXPANDED; privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, accessible, &stateData); } else if (eventType.LowerCaseEqualsLiteral("popuphiding")) { // If accessible focus was 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. if (!gLastFocusedNode) { return NS_OK; } nsCOMPtr parentOfFocus; gLastFocusedNode->GetParentNode(getter_AddRefs(parentOfFocus)); if (parentOfFocus != targetNode) { return NS_OK; } // Focus was inside of popup that's being hidden FireCurrentFocusEvent(); } else if (eventType.LowerCaseEqualsLiteral("popupshown")) { FireAccessibleFocusEvent(accessible, targetNode, aEvent); } else if (eventType.EqualsLiteral("DOMMenuInactive")) { nsCOMPtr popup(do_QueryInterface(targetNode)); if (popup) { privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUPEND, accessible, nsnull); } } #endif return NS_OK; } void nsRootAccessible::GetTargetNode(nsIDOMEvent *aEvent, nsIDOMNode **aTargetNode) { *aTargetNode = nsnull; nsCOMPtr nsevent(do_QueryInterface(aEvent)); if (nsevent) { nsCOMPtr domEventTarget; nsevent->GetOriginalTarget(getter_AddRefs(domEventTarget)); nsCOMPtr content(do_QueryInterface(domEventTarget)); nsIContent *bindingParent; if (content && content->IsContentOfType(nsIContent::eHTML) && (bindingParent = content->GetBindingParent()) != nsnull) { // Use binding parent when the event occurs in // anonymous HTML content. // This gets the following important cases correct: // 1. Inserted buttons like OK, Cancel, Help. // 2. XUL menulists and comboboxes. // 3. The focused radio button in a group. CallQueryInterface(bindingParent, aTargetNode); NS_ASSERTION(*aTargetNode, "No target node for binding parent of anonymous event target"); return; } if (domEventTarget) { CallQueryInterface(domEventTarget, aTargetNode); } } } void nsRootAccessible::FireFocusCallback(nsITimer *aTimer, void *aClosure) { nsRootAccessible *rootAccessible = NS_STATIC_CAST(nsRootAccessible*, aClosure); NS_ASSERTION(rootAccessible, "How did we get here without a root accessible?"); rootAccessible->FireCurrentFocusEvent(); } // ------- nsIDOMFocusListener Methods (1) ------------- NS_IMETHODIMP nsRootAccessible::Focus(nsIDOMEvent* aEvent) { return HandleEvent(aEvent); } NS_IMETHODIMP nsRootAccessible::Blur(nsIDOMEvent* aEvent) { return NS_OK; } // ------- nsIDOMFormListener Methods (5) ------------- NS_IMETHODIMP nsRootAccessible::Submit(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::Reset(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::Change(nsIDOMEvent* aEvent) { // get change events when the form elements changes its state, checked->not, // deleted text, new text, change in selection for list/combo boxes // this may be the event that we have the individual Accessible objects // handle themselves -- have list/combos figure out the change in selection // have textareas and inputs fire a change of state etc... return NS_OK; // Ignore form change events in MSAA } // gets Select events when text is selected in a textarea or input NS_IMETHODIMP nsRootAccessible::Select(nsIDOMEvent* aEvent) { return HandleEvent(aEvent); } // gets Input events when text is entered or deleted in a textarea or input NS_IMETHODIMP nsRootAccessible::Input(nsIDOMEvent* aEvent) { return NS_OK; } // ------- nsIDOMXULListener Methods (8) --------------- NS_IMETHODIMP nsRootAccessible::PopupShowing(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::PopupShown(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::PopupHiding(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::PopupHidden(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::Close(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::Command(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::Broadcast(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::CommandUpdate(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsRootAccessible::Shutdown() { // Called manually or by nsAccessNode::~nsAccessNode() if (!mWeakShell) { return NS_OK; // Already shutdown } mCaretAccessible = nsnull; mAccService = nsnull; if (mFireFocusTimer) { mFireFocusTimer->Cancel(); mFireFocusTimer = nsnull; } return nsDocAccessibleWrap::Shutdown(); } already_AddRefed nsRootAccessible::GetContentDocShell(nsIDocShellTreeItem *aStart) { PRInt32 itemType; aStart->GetItemType(&itemType); if (itemType == nsIDocShellTreeItem::typeContent) { nsCOMPtr accDoc = GetDocAccessibleFor(aStart); nsCOMPtr accessible = do_QueryInterface(accDoc); // If ancestor chain of accessibles is not completely visible, // don't use this one. This happens for example if it's inside // a background tab (tabbed browsing) while (accessible) { PRUint32 state; accessible->GetFinalState(&state); if (state & STATE_INVISIBLE) { return nsnull; } nsCOMPtr ancestor; accessible->GetParent(getter_AddRefs(ancestor)); accessible.swap(ancestor); } NS_ADDREF(aStart); return aStart; } nsCOMPtr treeNode(do_QueryInterface(aStart)); if (treeNode) { PRInt32 subDocuments; treeNode->GetChildCount(&subDocuments); for (PRInt32 count = 0; count < subDocuments; count ++) { nsCOMPtr treeItemChild, contentTreeItem; treeNode->GetChildAt(count, getter_AddRefs(treeItemChild)); NS_ENSURE_TRUE(treeItemChild, nsnull); contentTreeItem = GetContentDocShell(treeItemChild); if (contentTreeItem) { NS_ADDREF(aStart = contentTreeItem); return aStart; } } } return nsnull; } NS_IMETHODIMP nsRootAccessible::GetAccessibleRelated(PRUint32 aRelationType, nsIAccessible **aRelated) { *aRelated = nsnull; if (!mDOMNode || aRelationType != RELATION_EMBEDS) { return nsDocAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated); } nsCOMPtr treeItem = GetDocShellTreeItemFor(mDOMNode); nsCOMPtr contentTreeItem = GetContentDocShell(treeItem); nsCOMPtr accDoc = GetDocAccessibleFor(contentTreeItem); return accDoc->QueryInterface(NS_GET_IID(nsIAccessible), (void**)aRelated); }