/* -*- 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 "nsHTMLSelectAccessible.h" #include "nsIBaseWindow.h" #include "nsICaret.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeNode.h" #include "nsIDocShellTreeOwner.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMEventListener.h" #include "nsIDOMEventTarget.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsIDOMHTMLImageElement.h" #include "nsIDOMHTMLInputElement.h" #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 "nsIEventListenerManager.h" #include "nsIFocusController.h" #include "nsIFrame.h" #include "nsIMenuFrame.h" #include "nsIHTMLDocument.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIMenuParent.h" #include "nsIScrollableView.h" #include "nsISelectionPrivate.h" #include "nsIServiceManager.h" #include "nsIViewManager.h" #include "nsPIDOMWindow.h" #include "nsIWebBrowserChrome.h" #include "nsReadableUtils.h" #include "nsRootAccessible.h" #include "nsIDOMNSEventTarget.h" #include "nsIDOMDocumentEvent.h" #ifdef MOZ_XUL #include "nsXULTreeAccessible.h" #include "nsIXULDocument.h" #include "nsIXULWindow.h" #endif #ifdef MOZ_ACCESSIBILITY_ATK #include "nsAppRootAccessible.h" #else #include "nsApplicationAccessibleWrap.h" #endif // Expanded version of NS_IMPL_ISUPPORTS_INHERITED2 // so we can QI directly to concrete nsRootAccessible NS_IMPL_QUERY_HEAD(nsRootAccessible) NS_IMPL_QUERY_BODY(nsIDOMEventListener) if (aIID.Equals(NS_GET_IID(nsRootAccessible))) foundInterface = reinterpret_cast(this); else NS_IMPL_QUERY_TAIL_INHERITING(nsDocAccessible) NS_IMPL_ADDREF_INHERITED(nsRootAccessible, nsDocAccessible) NS_IMPL_RELEASE_INHERITED(nsRootAccessible, nsDocAccessible) //----------------------------------------------------- // construction //----------------------------------------------------- nsRootAccessible::nsRootAccessible(nsIDOMNode *aDOMNode, nsIWeakReference* aShell): nsDocAccessibleWrap(aDOMNode, aShell) { } //----------------------------------------------------- // 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; } } nsCOMPtr docShellAsItem = nsAccUtils::GetDocShellTreeItemFor(mDOMNode); NS_ENSURE_TRUE(docShellAsItem, 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) { NS_ENSURE_ARG_POINTER(aParent); *aParent = nsnull; nsRefPtr root = GetApplicationAccessible(); NS_IF_ADDREF(*aParent = root); 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 nsIAccessibleRole::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 = nsIAccessibleRole::ROLE_DIALOG; // Always at the root return NS_OK; } } } return nsDocAccessibleWrap::GetRole(aRole); } #ifdef MOZ_XUL PRUint32 nsRootAccessible::GetChromeFlags() { // Return the flag set for the top level window as defined // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME] // Not simple: nsIXULWindow is not just a QI from nsIDOMWindow nsCOMPtr treeItem = nsAccUtils::GetDocShellTreeItemFor(mDOMNode); NS_ENSURE_TRUE(treeItem, 0); nsCOMPtr treeOwner; treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); NS_ENSURE_TRUE(treeOwner, 0); nsCOMPtr xulWin(do_GetInterface(treeOwner)); if (!xulWin) { return 0; } PRUint32 chromeFlags; xulWin->GetChromeFlags(&chromeFlags); return chromeFlags; } #endif NS_IMETHODIMP nsRootAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState) { nsresult rv = nsDocAccessibleWrap::GetState(aState, aExtraState); NS_ENSURE_SUCCESS(rv, rv); if (!mDOMNode) return NS_OK; #ifdef MOZ_XUL PRUint32 chromeFlags = GetChromeFlags(); if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) { *aState |= nsIAccessibleStates::STATE_SIZEABLE; } if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR) { // If it has a titlebar it's movable // XXX unless it's minimized or maximized, but not sure // how to detect that *aState |= nsIAccessibleStates::STATE_MOVEABLE; } #endif if (!aExtraState) return NS_OK; nsCOMPtr domWin; GetWindow(getter_AddRefs(domWin)); nsCOMPtr privateDOMWindow(do_QueryInterface(domWin)); if (privateDOMWindow) { nsIFocusController *focusController = privateDOMWindow->GetRootFocusController(); if (focusController) { PRBool isActive = PR_FALSE; focusController->GetActive(&isActive); if (isActive) { *aExtraState |= nsIAccessibleStates::EXT_STATE_ACTIVE; } } } #ifdef MOZ_XUL if (GetChromeFlags() & nsIWebBrowserChrome::CHROME_MODAL) { *aExtraState |= nsIAccessibleStates::EXT_STATE_MODAL; } #endif 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); } const char* const docEvents[] = { #ifdef DEBUG // Capture mouse over events and fire fake DRAGDROPSTART event to simplify // debugging a11y objects with event viewers "mouseover", #endif // capture DOM focus events "focus", // capture Form change events "select", // capture ValueChange events (fired whenever value changes, immediately after, whether focus moves or not) "ValueChange", // capture AlertActive events (fired whenever alert pops up) "AlertActive", // add ourself as a TreeViewChanged listener (custom event fired in nsTreeBodyFrame.cpp) "TreeViewChanged", "TreeRowCountChanged", "TreeInvalidated", // add ourself as a OpenStateChange listener (custom event fired in tree.xml) "OpenStateChange", // add ourself as a CheckboxStateChange listener (custom event fired in nsHTMLInputElement.cpp) "CheckboxStateChange", // add ourself as a RadioStateChange Listener ( custom event fired in in nsHTMLInputElement.cpp & radio.xml) "RadioStateChange", "popupshown", "popuphiding", "DOMMenuInactive", "DOMMenuItemActive", "DOMMenuBarActive", "DOMMenuBarInactive", "DOMContentLoaded" }; nsresult nsRootAccessible::AddEventListeners() { // nsIDOMNSEventTarget interface allows to register event listeners to // receive untrusted events (synthetic events generated by untrusted code). // For example, XBL bindings implementations for elements that are hosted in // non chrome document fire untrusted events. nsCOMPtr nstarget(do_QueryInterface(mDocument)); if (nstarget) { for (const char* const* e = docEvents, * const* e_end = docEvents + NS_ARRAY_LENGTH(docEvents); e < e_end; ++e) { nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), this, PR_TRUE, PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); } } nsCOMPtr target; GetChromeEventHandler(getter_AddRefs(target)); if (target) { target->AddEventListener(NS_LITERAL_STRING("pagehide"), this, PR_TRUE); } if (!mCaretAccessible) { mCaretAccessible = new nsCaretAccessible(this); } return nsDocAccessible::AddEventListeners(); } nsresult nsRootAccessible::RemoveEventListeners() { nsCOMPtr target(do_QueryInterface(mDocument)); if (target) { for (const char* const* e = docEvents, * const* e_end = docEvents + NS_ARRAY_LENGTH(docEvents); e < e_end; ++e) { nsresult rv = target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); } } GetChromeEventHandler(getter_AddRefs(target)); if (target) { target->RemoveEventListener(NS_LITERAL_STRING("pagehide"), this, PR_TRUE); } // Do this before removing clearing caret accessible, so that it can use // shutdown the caret accessible's selection listener nsDocAccessible::RemoveEventListeners(); if (mCaretAccessible) { mCaretAccessible->Shutdown(); mCaretAccessible = nsnull; } return NS_OK; } nsCaretAccessible* nsRootAccessible::GetCaretAccessible() { return mCaretAccessible; } void nsRootAccessible::TryFireEarlyLoadEvent(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 = nsAccUtils::GetDocShellTreeItemFor(aDocNode); NS_ASSERTION(treeItem, "No docshelltreeitem for aDocNode"); if (!treeItem) { return; } PRInt32 itemType; treeItem->GetItemType(&itemType); if (itemType != nsIDocShellTreeItem::typeContent) { return; } // The doc accessible should already be created as a result of the // OnStateChange() for the initiation of page loading 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 == treeItem) { // No frames or iframes, so we can fire the doc load finished event early FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_INTERNAL_LOAD, aDocNode); } } PRBool nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible, nsIDOMNode *aNode, nsIDOMEvent *aFocusEvent, PRBool aForceEvent, PRBool aIsAsynch) { 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)); if (!realFocusedNode) { // When FireCurrentFocusEvent() synthesizes a focus event, // the orignal target does not exist, so use the passed-in node // which is the relevant focused node realFocusedNode = aNode; } if (realFocusedNode) { mCaretAccessible->SetControlSelectionListener(realFocusedNode); } } } // Check for aria-activedescendant, which changes which element has focus nsCOMPtr finalFocusNode = aNode; nsCOMPtr finalFocusAccessible = aAccessible; nsCOMPtr finalFocusContent = GetRoleContent(finalFocusNode); if (finalFocusContent) { nsAutoString id; if (finalFocusContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_activedescendant, id)) { nsCOMPtr domDoc; aNode->GetOwnerDocument(getter_AddRefs(domDoc)); if (!domDoc) { // Maybe the passed-in node actually is a doc domDoc = do_QueryInterface(aNode); } if (!domDoc) { return PR_FALSE; } nsCOMPtr relatedEl; domDoc->GetElementById(id, getter_AddRefs(relatedEl)); finalFocusNode = do_QueryInterface(relatedEl); if (!finalFocusNode) { // If aria-activedescendant is set to nonextistant ID, then treat as focus // on the activedescendant container (which has real DOM focus) finalFocusNode = aNode; } finalFocusAccessible = nsnull; } } // Fire focus only if it changes, but always fire focus events when aForceEvent == PR_TRUE if (gLastFocusedNode == finalFocusNode && !aForceEvent) { return PR_FALSE; } if (!finalFocusAccessible) { GetAccService()->GetAccessibleFor(finalFocusNode, getter_AddRefs(finalFocusAccessible)); // For activedescendant, the ARIA spec does not require that the user agent // checks whether finalFocusNode is actually a descendant of the element with // the activedescendant attribute. if (!finalFocusAccessible) { return PR_FALSE; } } gLastFocusedAccessiblesState = State(finalFocusAccessible); PRUint32 role = Role(finalFocusAccessible); if (role == nsIAccessibleRole::ROLE_MENUITEM) { if (!mCurrentARIAMenubar) { // Entering menus PRUint32 naturalRole; // The natural role is the role that this type of element normally has finalFocusAccessible->GetRole(&naturalRole); if (role != naturalRole) { // Must be a DHTML menuitem nsCOMPtr menuBarAccessible = nsAccUtils::GetAncestorWithRole(finalFocusAccessible, nsIAccessibleRole::ROLE_MENUBAR); nsCOMPtr menuBarAccessNode = do_QueryInterface(menuBarAccessible); if (menuBarAccessNode) { menuBarAccessNode->GetDOMNode(getter_AddRefs(mCurrentARIAMenubar)); if (mCurrentARIAMenubar) { nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENU_START, menuBarAccessible); } } } } } else if (mCurrentARIAMenubar) { nsCOMPtr menuEndEvent = new nsAccEvent(nsIAccessibleEvent::EVENT_MENU_END, mCurrentARIAMenubar, PR_FALSE, nsAccEvent::eAllowDupes); if (menuEndEvent) { FireDelayedAccessibleEvent(menuEndEvent); } mCurrentARIAMenubar = nsnull; } NS_IF_RELEASE(gLastFocusedNode); gLastFocusedNode = finalFocusNode; NS_IF_ADDREF(gLastFocusedNode); nsCOMPtr focusContent = do_QueryInterface(gLastFocusedNode); nsIFrame *focusFrame = nsnull; if (focusContent) { nsCOMPtr shell = nsAccessNode::GetPresShellFor(gLastFocusedNode); focusFrame = shell->GetRealPrimaryFrameFor(focusContent); } gLastFocusedFrameType = (focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ? focusFrame->GetType() : 0; nsCOMPtr docAccessible = do_QueryInterface(finalFocusAccessible); if (docAccessible) { // Doc is gaining focus, but actual focus may be on an element within document nsCOMPtr realFocusedNode = GetCurrentFocus(); if (realFocusedNode != aNode || realFocusedNode == mDOMNode) { // Suppress document focus, because real DOM focus will be fired next, // and that's what we care about // Make sure we never fire focus for the nsRootAccessible (mDOMNode) return PR_FALSE; } } FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, finalFocusNode, nsAccEvent::eRemoveDupes, aIsAsynch); return PR_TRUE; } void nsRootAccessible::FireCurrentFocusEvent() { nsCOMPtr focusedNode = GetCurrentFocus(); if (!focusedNode) { return; // No current focus } // Simulate a focus event so that we can reuse code that fires focus for container children like treeitems nsCOMPtr docEvent = do_QueryInterface(mDocument); if (docEvent) { nsCOMPtr event; if (NS_SUCCEEDED(docEvent->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event))) && NS_SUCCEEDED(event->InitEvent(NS_LITERAL_STRING("focus"), PR_TRUE, PR_TRUE))) { // Get the target node we really want for the event. nsIAccessibilityService* accService = GetAccService(); if (accService) { nsCOMPtr targetNode; accService->GetRelevantContentNodeFor(focusedNode, getter_AddRefs(targetNode)); if (targetNode) { HandleEventWithTarget(event, targetNode); } } } } } // --------------- 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; return HandleEventWithTarget(aEvent, targetNode); } nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent, nsIDOMNode* aTargetNode) { nsAutoString eventType; aEvent->GetType(eventType); nsAutoString localName; aTargetNode->GetLocalName(localName); #ifdef MOZ_XUL PRBool isTree = localName.EqualsLiteral("tree"); #endif #ifdef DEBUG_A11Y // Very useful for debugging, please leave this here. if (eventType.EqualsLiteral("AlertActive")) { printf("\ndebugging %s events for %s", NS_ConvertUTF16toUTF8(eventType).get(), NS_ConvertUTF16toUTF8(localName).get()); } if (localName.LowerCaseEqualsLiteral("textbox")) { printf("\ndebugging %s events for %s", NS_ConvertUTF16toUTF8(eventType).get(), NS_ConvertUTF16toUTF8(localName).get()); } #endif nsIAccessibilityService *accService = GetAccService(); NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE); if (eventType.EqualsLiteral("pagehide")) { // pagehide event can be fired under several conditions, such as HTML // document going away, closing a window/dialog, and wizard page changing. // We only destroy the accessible object when it's a document accessible, // so that we don't destroy something still in use, like wizard page. // And we only get cached document accessible to destroy, so that we don't // create it just to destroy it. nsCOMPtr doc(do_QueryInterface(aTargetNode)); nsCOMPtr accDoc = GetDocAccessibleFor(doc); nsCOMPtr privateAcc = do_QueryInterface(accDoc); if (privateAcc) { privateAcc->Shutdown(); } return NS_OK; } nsCOMPtr eventShell = GetPresShellFor(aTargetNode); if (!eventShell) { return NS_OK; } if (eventType.EqualsLiteral("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(aTargetNode); return NS_OK; } #ifdef MOZ_XUL if (eventType.EqualsLiteral("TreeViewChanged")) { // Always asynch, always from user input if (!isTree) return NS_OK; nsCOMPtr treeContent = do_QueryInterface(aTargetNode); nsAccEvent::PrepareForEvent(aTargetNode, PR_TRUE); return accService->InvalidateSubtreeFor(eventShell, treeContent, nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE); } #endif if (eventType.EqualsLiteral("popuphiding")) { // 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 if (!gLastFocusedNode || !nsAccUtils::IsAncestorOf(aTargetNode, gLastFocusedNode)) { return NS_OK; // And was not focused on an item inside the popup } // Focus was on or inside of a popup that's being hidden FireCurrentFocusEvent(); } nsCOMPtr accessible; accService->GetAccessibleInShell(aTargetNode, eventShell, getter_AddRefs(accessible)); nsCOMPtr privAcc(do_QueryInterface(accessible)); if (!privAcc) return NS_OK; #ifdef MOZ_XUL if (eventType.EqualsLiteral("TreeRowCountChanged")) return HandleTreeRowCountChangedEvent(aEvent, accessible, localName); if (eventType.EqualsLiteral("TreeInvalidated")) return HandleTreeInvalidatedEvent(aEvent, accessible, localName); #endif if (eventType.EqualsLiteral("RadioStateChange")) { PRUint32 state = State(accessible); // radiogroup in prefWindow is exposed as a list, // and panebutton is exposed as XULListitem in A11y. // nsXULListitemAccessible::GetState uses STATE_SELECTED in this case, // so we need to check nsIAccessibleStates::STATE_SELECTED also. PRBool isEnabled = (state & (nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_SELECTED)) != 0; nsCOMPtr accEvent = new nsAccStateChangeEvent(accessible, nsIAccessibleStates::STATE_CHECKED, PR_FALSE, isEnabled); privAcc->FireAccessibleEvent(accEvent); if (isEnabled) FireAccessibleFocusEvent(accessible, aTargetNode, aEvent); return NS_OK; } if (eventType.EqualsLiteral("CheckboxStateChange")) { PRUint32 state = State(accessible); PRBool isEnabled = !!(state & nsIAccessibleStates::STATE_CHECKED); nsCOMPtr accEvent = new nsAccStateChangeEvent(accessible, nsIAccessibleStates::STATE_CHECKED, PR_FALSE, isEnabled); return privAcc->FireAccessibleEvent(accEvent); } nsCOMPtr treeItemAccessible; #ifdef MOZ_XUL // If it's a tree element, need the currently selected item if (isTree) { nsCOMPtr multiSelect = do_QueryInterface(aTargetNode); 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 #ifdef MOZ_XUL if (treeItemAccessible && eventType.EqualsLiteral("OpenStateChange")) { PRUint32 state = State(accessible); // collapsed/expanded changed PRBool isEnabled = (state & nsIAccessibleStates::STATE_EXPANDED) != 0; nsCOMPtr accEvent = new nsAccStateChangeEvent(accessible, nsIAccessibleStates::STATE_EXPANDED, PR_FALSE, isEnabled); return FireAccessibleEvent(accEvent); } if (treeItemAccessible && eventType.EqualsLiteral("select")) { // If multiselect tree, we should fire selectionadd or selection removed if (gLastFocusedNode == aTargetNode) { nsCOMPtr multiSel = do_QueryInterface(aTargetNode); nsAutoString selType; multiSel->GetSelType(selType); if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { // 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(); return nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, accessible); } return nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_SELECTION, treeItemAccessible); } } else #endif if (eventType.EqualsLiteral("focus")) { if (aTargetNode == mDOMNode && mDOMNode != gLastFocusedNode) { // Got focus event for the window, we will make sure that an accessible // focus event for initial focus is fired. We do this on a short timer // because the initial focus may not have been set yet. if (!mFireFocusTimer) { mFireFocusTimer = do_CreateInstance("@mozilla.org/timer;1"); } if (mFireFocusTimer) { mFireFocusTimer->InitWithFuncCallback(FireFocusCallback, this, 0, nsITimer::TYPE_ONE_SHOT); } } // 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 focusedItem(aTargetNode); if (!treeItemAccessible) { nsCOMPtr selectControl = do_QueryInterface(aTargetNode); if (selectControl) { nsCOMPtr menuList = do_QueryInterface(aTargetNode); 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) focusedItem = do_QueryInterface(selectedItem); if (!focusedItem) return NS_OK; accService->GetAccessibleInShell(focusedItem, eventShell, getter_AddRefs(accessible)); if (!accessible) return NS_OK; } } } FireAccessibleFocusEvent(accessible, focusedItem, aEvent); } else if (eventType.EqualsLiteral("AlertActive")) { nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); } else if (eventType.EqualsLiteral("popupshown")) { // Don't fire menupopup events for combobox and autocomplete lists PRUint32 role = Role(accessible); PRInt32 event = 0; if (role == nsIAccessibleRole::ROLE_MENUPOPUP) { event = nsIAccessibleEvent::EVENT_MENUPOPUP_START; } else if (role == nsIAccessibleRole::ROLE_TOOLTIP) { // There is a single node which Mozilla moves around. // The accessible for it stays the same no matter where it moves. // AT's expect to get an EVENT_SHOW for the tooltip. // In event callback the tooltip's accessible will be ready. event = nsIAccessibleEvent::EVENT_ASYNCH_SHOW; } if (event) { nsAccUtils::FireAccEvent(event, accessible); } } else if (eventType.EqualsLiteral("DOMMenuInactive")) { if (Role(accessible) == nsIAccessibleRole::ROLE_MENUPOPUP) { nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, accessible); } } else if (eventType.EqualsLiteral("DOMMenuItemActive")) { PRBool fireFocus = PR_FALSE; if (!treeItemAccessible) { #ifdef MOZ_XUL if (isTree) { return NS_OK; // Tree with nothing selected } #endif nsCOMPtr menuAccessNode = do_QueryInterface(accessible); NS_ENSURE_TRUE(menuAccessNode, NS_ERROR_FAILURE); nsIFrame* menuFrame = menuAccessNode->GetFrame(); NS_ENSURE_TRUE(menuFrame, NS_ERROR_FAILURE); nsIMenuFrame* imenuFrame; CallQueryInterface(menuFrame, &imenuFrame); if (imenuFrame) fireFocus = PR_TRUE; // QI failed for nsIMenuFrame means it's not on menu bar if (imenuFrame && imenuFrame->IsOnMenuBar() && !imenuFrame->IsOnActiveMenuBar()) { // It is a top level menuitem. Only fire a focus event when the menu bar // is active. return NS_OK; } else { nsCOMPtr containerAccessible; accessible->GetParent(getter_AddRefs(containerAccessible)); NS_ENSURE_TRUE(containerAccessible, NS_ERROR_FAILURE); // 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 (State(containerAccessible) & nsIAccessibleStates::STATE_COLLAPSED) { nsCOMPtr containerParent; containerAccessible->GetParent(getter_AddRefs(containerParent)); NS_ENSURE_TRUE(containerParent, NS_ERROR_FAILURE); if (Role(containerParent) != nsIAccessibleRole::ROLE_COMBOBOX) { return NS_OK; } } } } if (!fireFocus) { nsCOMPtr realFocusedNode = GetCurrentFocus(); nsCOMPtr realFocusedContent = do_QueryInterface(realFocusedNode); nsCOMPtr targetContent = do_QueryInterface(aTargetNode); nsIContent *containerContent = targetContent; while (containerContent) { nsCOMPtr 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) { nsAccEvent::PrepareForEvent(aTargetNode, PR_TRUE); // Always asynch, always from user input FireAccessibleFocusEvent(accessible, aTargetNode, aEvent, PR_TRUE, PR_TRUE); } } else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always asynch, always from user input nsAccEvent::PrepareForEvent(aTargetNode, PR_TRUE); nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENU_START, accessible, PR_TRUE); } else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always asynch, always from user input nsAccEvent::PrepareForEvent(aTargetNode, PR_TRUE); nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENU_END, accessible, PR_TRUE); FireCurrentFocusEvent(); } else if (eventType.EqualsLiteral("ValueChange")) { FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aTargetNode, nsAccEvent::eRemoveDupes); } #ifdef DEBUG else if (eventType.EqualsLiteral("mouseover")) { nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, accessible); } #endif return NS_OK; } void nsRootAccessible::GetTargetNode(nsIDOMEvent *aEvent, nsIDOMNode **aTargetNode) { *aTargetNode = nsnull; nsCOMPtr nsevent(do_QueryInterface(aEvent)); if (!nsevent) return; nsCOMPtr domEventTarget; nsevent->GetOriginalTarget(getter_AddRefs(domEventTarget)); nsCOMPtr eventTarget(do_QueryInterface(domEventTarget)); if (!eventTarget) return; nsIAccessibilityService* accService = GetAccService(); if (accService) { nsresult rv = accService->GetRelevantContentNodeFor(eventTarget, aTargetNode); if (NS_SUCCEEDED(rv) && *aTargetNode) return; } NS_ADDREF(*aTargetNode = eventTarget); } void nsRootAccessible::FireFocusCallback(nsITimer *aTimer, void *aClosure) { nsRootAccessible *rootAccessible = static_cast(aClosure); NS_ASSERTION(rootAccessible, "How did we get here without a root accessible?"); rootAccessible->FireCurrentFocusEvent(); } NS_IMETHODIMP nsRootAccessible::Init() { nsresult rv = nsDocAccessibleWrap::Init(); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr root = GetApplicationAccessible(); NS_ENSURE_STATE(root); root->AddRootAccessible(this); return NS_OK; } NS_IMETHODIMP nsRootAccessible::Shutdown() { // Called manually or by nsAccessNode::LastRelease() if (!mWeakShell) { return NS_OK; // Already shutdown } nsRefPtr root = GetApplicationAccessible(); NS_ENSURE_STATE(root); root->RemoveRootAccessible(this); mCurrentARIAMenubar = nsnull; if (mFireFocusTimer) { mFireFocusTimer->Cancel(); mFireFocusTimer = nsnull; } return nsDocAccessibleWrap::Shutdown(); } already_AddRefed nsRootAccessible::GetContentDocShell(nsIDocShellTreeItem *aStart) { if (!aStart) { return nsnull; } PRInt32 itemType; aStart->GetItemType(&itemType); if (itemType == nsIDocShellTreeItem::typeContent) { nsCOMPtr accDoc = GetDocAccessibleFor(aStart, PR_TRUE); 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) { if (State(accessible) & nsIAccessibleStates::STATE_INVISIBLE) { return nsnull; } nsCOMPtr ancestor; accessible->GetParent(getter_AddRefs(ancestor)); if (ancestor == this) { break; // Don't check past original root accessible we started with } 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 != nsIAccessibleRelation::RELATION_EMBEDS) { return nsDocAccessibleWrap::GetAccessibleRelated(aRelationType, aRelated); } nsCOMPtr treeItem = nsAccUtils::GetDocShellTreeItemFor(mDOMNode); nsCOMPtr contentTreeItem = GetContentDocShell(treeItem); // there may be no content area, so we need a null check if (contentTreeItem) { nsCOMPtr accDoc = GetDocAccessibleFor(contentTreeItem, PR_TRUE); NS_ASSERTION(accDoc, "No EMBEDS document"); if (accDoc) { accDoc->QueryInterface(NS_GET_IID(nsIAccessible), (void**)aRelated); } } return NS_OK; } NS_IMETHODIMP nsRootAccessible::FireDocLoadEvents(PRUint32 aEventType) { if (!mDocument || !mWeakShell) { return NS_OK; // Document has been shut down } nsCOMPtr docShellTreeItem = nsAccUtils::GetDocShellTreeItemFor(mDOMNode); NS_ASSERTION(docShellTreeItem, "No doc shell tree item for document"); NS_ENSURE_TRUE(docShellTreeItem, NS_ERROR_FAILURE); PRInt32 contentType; docShellTreeItem->GetItemType(&contentType); if (contentType == nsIDocShellTreeItem::typeContent) { return nsDocAccessibleWrap::FireDocLoadEvents(aEventType); // Content might need to fire event } // Root chrome: don't fire event mIsContentLoaded = (aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE || aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED); return NS_OK; } nsresult nsRootAccessible::HandleTreeRowCountChangedEvent(nsIDOMEvent *aEvent, nsIAccessible *aAccessible, const nsAString& aTargetName) { if (!aTargetName.EqualsLiteral("tree")) return NS_OK; nsCOMPtr dataEvent(do_QueryInterface(aEvent)); if (!dataEvent) return NS_OK; nsCOMPtr indexVariant; dataEvent->GetData(NS_LITERAL_STRING("index"), getter_AddRefs(indexVariant)); if (!indexVariant) return NS_OK; nsCOMPtr countVariant; dataEvent->GetData(NS_LITERAL_STRING("count"), getter_AddRefs(countVariant)); if (!countVariant) return NS_OK; PRInt32 index, count; indexVariant->GetAsInt32(&index); countVariant->GetAsInt32(&count); nsCOMPtr treeAccCache(do_QueryInterface(aAccessible)); NS_ENSURE_STATE(treeAccCache); return treeAccCache->InvalidateCache(index, count); } nsresult nsRootAccessible::HandleTreeInvalidatedEvent(nsIDOMEvent *aEvent, nsIAccessible *aAccessible, const nsAString& aTargetName) { if (!aTargetName.EqualsLiteral("tree")) return NS_OK; nsCOMPtr dataEvent(do_QueryInterface(aEvent)); if (!dataEvent) return NS_OK; PRInt32 startRow = 0, endRow = -1, startCol = 0, endCol = -1; nsCOMPtr startRowVariant; dataEvent->GetData(NS_LITERAL_STRING("startrow"), getter_AddRefs(startRowVariant)); if (startRowVariant) startRowVariant->GetAsInt32(&startRow); nsCOMPtr endRowVariant; dataEvent->GetData(NS_LITERAL_STRING("endrow"), getter_AddRefs(endRowVariant)); if (endRowVariant) endRowVariant->GetAsInt32(&endRow); nsCOMPtr startColVariant; dataEvent->GetData(NS_LITERAL_STRING("startcolumn"), getter_AddRefs(startColVariant)); if (startColVariant) startColVariant->GetAsInt32(&startCol); nsCOMPtr endColVariant; dataEvent->GetData(NS_LITERAL_STRING("endcolumn"), getter_AddRefs(endColVariant)); if (endColVariant) endColVariant->GetAsInt32(&endCol); nsCOMPtr treeAcc(do_QueryInterface(aAccessible)); NS_ENSURE_STATE(treeAcc); return treeAcc->TreeViewInvalidated(startRow, endRow, startCol, endCol); }