/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 sw=2 et tw=80: */ /* ***** 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): * Makoto Kato * Dean Tessman * Mats Palmgren * Simon Bünzli * * 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 ***** */ #include "nsCOMPtr.h" #include "nsEventStateManager.h" #include "nsEventListenerManager.h" #include "nsIContent.h" #include "nsINodeInfo.h" #include "nsIDocument.h" #include "nsIFrame.h" #include "nsIWidget.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "nsDOMEvent.h" #include "nsHTMLAtoms.h" #include "nsIEditorDocShell.h" #include "nsIFormControl.h" #include "nsIComboboxControlFrame.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsIDOMHTMLInputElement.h" #include "nsIDOMNSHTMLInputElement.h" #include "nsIDOMHTMLSelectElement.h" #include "nsIDOMHTMLTextAreaElement.h" #include "nsIDOMHTMLAreaElement.h" #include "nsIDOMHTMLButtonElement.h" #include "nsIDOMHTMLObjectElement.h" #include "nsIDOMHTMLImageElement.h" #include "nsIDOMHTMLMapElement.h" #include "nsIDOMHTMLBodyElement.h" #include "nsIDOMXULControlElement.h" #include "nsImageMapUtils.h" #include "nsIHTMLDocument.h" #include "nsINameSpaceManager.h" #include "nsIBaseWindow.h" #include "nsIScrollableView.h" #include "nsISelection.h" #include "nsIFrameSelection.h" #include "nsIDeviceContext.h" #include "nsIScriptGlobalObject.h" #include "nsIPrivateDOMEvent.h" #include "nsIDOMWindowInternal.h" #include "nsPIDOMWindow.h" #include "nsIDOMEventTarget.h" #include "nsIEnumerator.h" #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeNode.h" #include "nsIWebNavigation.h" #include "nsIContentViewer.h" #include "nsIPrefBranch2.h" #include "nsIServiceManager.h" #include "nsIScriptSecurityManager.h" #include "nsIChromeEventHandler.h" #include "nsIFocusController.h" #include "nsXULAtoms.h" #include "nsIDOMXULElement.h" #include "nsIDOMDocument.h" #include "nsIDOMKeyEvent.h" #include "nsIObserverService.h" #include "nsIDocShell.h" #include "nsIMarkupDocumentViewer.h" #include "nsIScrollableViewProvider.h" #include "nsIDOMDocumentRange.h" #include "nsIDOMDocumentEvent.h" #include "nsIDOMMouseEvent.h" #include "nsIDOMEventTarget.h" #include "nsIDOMDocumentView.h" #include "nsIDOMAbstractView.h" #include "nsIDOMNSUIEvent.h" #include "nsIDOMRange.h" #include "nsICaret.h" #include "nsILookAndFeel.h" #include "nsWidgetsCID.h" #include "nsIFrameFrame.h" #include "nsIFrameTraversal.h" #include "nsLayoutAtoms.h" #include "nsLayoutCID.h" #include "nsLayoutUtils.h" #include "nsIInterfaceRequestorUtils.h" #include "nsUnicharUtils.h" #include "nsContentUtils.h" #include "imgIContainer.h" #include "nsIProperties.h" #include "nsISupportsPrimitives.h" #if defined (XP_MAC) || defined(XP_MACOSX) #include #endif #if defined(DEBUG_rods) || defined(DEBUG_bryner) //#define DEBUG_DOCSHELL_FOCUS #endif static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); //we will use key binding by default now. this wil lbreak viewer for now #define NON_KEYBINDING 0 static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID); nsIContent * gLastFocusedContent = 0; // Strong reference nsIDocument * gLastFocusedDocument = 0; // Strong reference nsPresContext* gLastFocusedPresContext = 0; // Weak reference enum nsTextfieldSelectModel { eTextfieldSelect_unset = -1, eTextfieldSelect_manual = 0, eTextfieldSelect_auto = 1 // select textfields when focused with keyboard }; // Tab focus policy (static, constant across the app): // Which types of elements are in the tab order? static PRInt8 sTextfieldSelectModel = eTextfieldSelect_unset; static PRBool sLeftClickOnly = PR_TRUE; static PRBool sKeyCausesActivation = PR_TRUE; static PRUint32 sESMInstanceCount = 0; static PRInt32 sChromeAccessModifier = 0, sContentAccessModifier = 0; PRInt32 nsEventStateManager::sUserInputEventDepth = 0; enum { MOUSE_SCROLL_N_LINES, MOUSE_SCROLL_PAGE, MOUSE_SCROLL_HISTORY, MOUSE_SCROLL_TEXTSIZE, MOUSE_SCROLL_PIXELS }; // mask values for ui.key.chromeAccess and ui.key.contentAccess #define NS_MODIFIER_SHIFT 1 #define NS_MODIFIER_CONTROL 2 #define NS_MODIFIER_ALT 4 #define NS_MODIFIER_META 8 static nsIScriptGlobalObject * GetDocumentOuterWindow(nsIDocument *aDocument) { if (aDocument) { nsIScriptGlobalObject *sgo = aDocument->GetScriptGlobalObject(); nsCOMPtr win = do_QueryInterface(sgo); if (win) { nsCOMPtr outersgo = do_QueryInterface(win->GetOuterWindow()); return outersgo; } return sgo; } return nsnull; } static nsIDocument * GetDocumentFromWindow(nsIDOMWindow *aWindow) { nsCOMPtr win = do_QueryInterface(aWindow); nsPIDOMWindow *innerWin; nsCOMPtr doc; if (win) { doc = do_QueryInterface(win->GetExtantDocument()); } return doc; } static PRInt32 GetAccessModifierMaskFromPref(PRInt32 aItemType) { PRInt32 accessKey = nsContentUtils::GetIntPref("ui.key.generalAccessKey", -1); switch (accessKey) { case -1: break; // use the individual prefs case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT; case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL; case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT; case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META; default: return 0; } switch (aItemType) { case nsIDocShellTreeItem::typeChrome: return nsContentUtils::GetIntPref("ui.key.chromeAccess", 0); case nsIDocShellTreeItem::typeContent: return nsContentUtils::GetIntPref("ui.key.contentAccess", 0); default: return 0; } } /******************************************************************/ /* nsEventStateManager */ /******************************************************************/ nsEventStateManager::nsEventStateManager() : mLockCursor(0), mCurrentTarget(nsnull), mLastMouseOverFrame(nsnull), mLastDragOverFrame(nsnull), // init d&d gesture state machine variables mGestureDownPoint(0,0), mCurrentFocusFrame(nsnull), mCurrentTabIndex(0), mLastFocusedWith(eEventFocusedByUnknown), mPresContext(nsnull), mLClickCount(0), mMClickCount(0), mRClickCount(0), mConsumeFocusEvents(PR_FALSE), mNormalLMouseEventInProcess(PR_FALSE), m_haveShutdown(PR_FALSE), mClearedFrameRefsDuringEvent(PR_FALSE), mBrowseWithCaret(PR_FALSE), mTabbedThroughDocument(PR_FALSE), mDOMEventLevel(0), mAccessKeys(nsnull) { ++sESMInstanceCount; } NS_IMETHODIMP nsEventStateManager::Init() { nsresult rv; nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE); nsCOMPtr prefBranch = do_QueryInterface(nsContentUtils::GetPrefBranch()); if (prefBranch) { if (sESMInstanceCount == 1) { sLeftClickOnly = nsContentUtils::GetBoolPref("nglayout.events.dispatchLeftClickOnly", sLeftClickOnly); sChromeAccessModifier = GetAccessModifierMaskFromPref(nsIDocShellTreeItem::typeChrome); sContentAccessModifier = GetAccessModifierMaskFromPref(nsIDocShellTreeItem::typeContent); nsIContent::sTabFocusModelAppliesToXUL = nsContentUtils::GetBoolPref("accessibility.tabfocus_applies_to_xul", nsIContent::sTabFocusModelAppliesToXUL); } prefBranch->AddObserver("accessibility.accesskeycausesactivation", this, PR_TRUE); prefBranch->AddObserver("accessibility.browsewithcaret", this, PR_TRUE); prefBranch->AddObserver("accessibility.tabfocus_applies_to_xul", this, PR_TRUE); prefBranch->AddObserver("nglayout.events.dispatchLeftClickOnly", this, PR_TRUE); prefBranch->AddObserver("ui.key.generalAccessKey", this, PR_TRUE); prefBranch->AddObserver("ui.key.chromeAccess", this, PR_TRUE); prefBranch->AddObserver("ui.key.contentAccess", this, PR_TRUE); #if 0 prefBranch->AddObserver("mousewheel.withaltkey.action", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withaltkey.numlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withaltkey.sysnumlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withcontrolkey.action", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withcontrolkey.numlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withcontrolkey.sysnumlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withnokey.action", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withnokey.numlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withnokey.sysnumlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withshiftkey.action", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withshiftkey.numlines", this, PR_TRUE); prefBranch->AddObserver("mousewheel.withshiftkey.sysnumlines", this, PR_TRUE); #endif prefBranch->AddObserver("dom.popup_allowed_events", this, PR_TRUE); } if (sTextfieldSelectModel == eTextfieldSelect_unset) { nsCOMPtr lookNFeel(do_GetService(kLookAndFeelCID)); PRInt32 selectTextfieldsOnKeyFocus = 0; lookNFeel->GetMetric(nsILookAndFeel::eMetric_SelectTextfieldsOnKeyFocus, selectTextfieldsOnKeyFocus); sTextfieldSelectModel = selectTextfieldsOnKeyFocus ? eTextfieldSelect_auto: eTextfieldSelect_manual; } return rv; } nsEventStateManager::~nsEventStateManager() { #if CLICK_HOLD_CONTEXT_MENUS KillClickHoldTimer(); #endif --sESMInstanceCount; if(sESMInstanceCount == 0) { NS_IF_RELEASE(gLastFocusedContent); NS_IF_RELEASE(gLastFocusedDocument); } delete mAccessKeys; if (!m_haveShutdown) { Shutdown(); // Don't remove from Observer service in Shutdown because Shutdown also // gets called from xpcom shutdown observer. And we don't want to remove // from the service in that case. nsresult rv; nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); if (NS_SUCCEEDED(rv)) { observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); } } } nsresult nsEventStateManager::Shutdown() { nsCOMPtr prefBranch = do_QueryInterface(nsContentUtils::GetPrefBranch()); if (prefBranch) { prefBranch->RemoveObserver("accessibility.accesskeycausesactivation", this); prefBranch->RemoveObserver("accessibility.browsewithcaret", this); prefBranch->RemoveObserver("accessibility.tabfocus_applies_to_xul", this); prefBranch->RemoveObserver("nglayout.events.dispatchLeftClickOnly", this); prefBranch->RemoveObserver("ui.key.generalAccessKey", this); prefBranch->RemoveObserver("ui.key.chromeAccess", this); prefBranch->RemoveObserver("ui.key.contentAccess", this); #if 0 prefBranch->RemoveObserver("mousewheel.withshiftkey.action", this); prefBranch->RemoveObserver("mousewheel.withshiftkey.numlines", this); prefBranch->RemoveObserver("mousewheel.withshiftkey.sysnumlines", this); prefBranch->RemoveObserver("mousewheel.withcontrolkey.action", this); prefBranch->RemoveObserver("mousewheel.withcontrolkey.numlines", this); prefBranch->RemoveObserver("mousewheel.withcontrolkey.sysnumlines", this); prefBranch->RemoveObserver("mousewheel.withaltkey.action", this); prefBranch->RemoveObserver("mousewheel.withaltkey.numlines", this); prefBranch->RemoveObserver("mousewheel.withaltkey.sysnumlines", this); prefBranch->RemoveObserver("mousewheel.withnokey.action", this); prefBranch->RemoveObserver("mousewheel.withnokey.numlines", this); prefBranch->RemoveObserver("mousewheel.withnokey.sysnumlines", this); #endif prefBranch->RemoveObserver("dom.popup_allowed_events", this); } m_haveShutdown = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsEventStateManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData) { if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) Shutdown(); else if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { if (!someData) return NS_OK; nsDependentString data(someData); if (data.EqualsLiteral("accessibility.accesskeycausesactivation")) { sKeyCausesActivation = nsContentUtils::GetBoolPref("accessibility.accesskeycausesactivation", sKeyCausesActivation); } else if (data.EqualsLiteral("accessibility.browsewithcaret")) { ResetBrowseWithCaret(); } else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) { nsIContent::sTabFocusModelAppliesToXUL = nsContentUtils::GetBoolPref("accessibility.tabfocus_applies_to_xul", nsIContent::sTabFocusModelAppliesToXUL); } else if (data.EqualsLiteral("nglayout.events.dispatchLeftClickOnly")) { sLeftClickOnly = nsContentUtils::GetBoolPref("nglayout.events.dispatchLeftClickOnly", sLeftClickOnly); } else if (data.EqualsLiteral("ui.key.generalAccessKey")) { sChromeAccessModifier = GetAccessModifierMaskFromPref(nsIDocShellTreeItem::typeChrome); sContentAccessModifier = GetAccessModifierMaskFromPref(nsIDocShellTreeItem::typeContent); } else if (data.EqualsLiteral("ui.key.chromeAccess")) { sChromeAccessModifier = GetAccessModifierMaskFromPref(nsIDocShellTreeItem::typeChrome); } else if (data.EqualsLiteral("ui.key.contentAccess")) { sContentAccessModifier = GetAccessModifierMaskFromPref(nsIDocShellTreeItem::typeContent); #if 0 } else if (data.EqualsLiteral("mousewheel.withaltkey.action")) { } else if (data.EqualsLiteral("mousewheel.withaltkey.numlines")) { } else if (data.EqualsLiteral("mousewheel.withaltkey.sysnumlines")) { } else if (data.EqualsLiteral("mousewheel.withcontrolkey.action")) { } else if (data.EqualsLiteral("mousewheel.withcontrolkey.numlines")) { } else if (data.EqualsLiteral("mousewheel.withcontrolkey.sysnumlines")) { } else if (data.EqualsLiteral("mousewheel.withshiftkey.action")) { } else if (data.EqualsLiteral("mousewheel.withshiftkey.numlines")) { } else if (data.EqualsLiteral("mousewheel.withshiftkey.sysnumlines")) { } else if (data.EqualsLiteral("mousewheel.withnokey.action")) { } else if (data.EqualsLiteral("mousewheel.withnokey.numlines")) { } else if (data.EqualsLiteral("mousewheel.withnokey.sysnumlines")) { #endif } else if (data.EqualsLiteral("dom.popup_allowed_events")) { nsDOMEvent::PopupAllowedEventsChanged(); } } return NS_OK; } NS_IMPL_ISUPPORTS3(nsEventStateManager, nsIEventStateManager, nsIObserver, nsISupportsWeakReference) inline void SetFrameExternalReference(nsIFrame* aFrame) { aFrame->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE); } NS_IMETHODIMP nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext, nsEvent *aEvent, nsIFrame* aTargetFrame, nsEventStatus* aStatus, nsIView* aView) { NS_ENSURE_ARG_POINTER(aStatus); NS_ENSURE_ARG(aPresContext); mCurrentTarget = aTargetFrame; mCurrentTargetContent = nsnull; // Focus events don't necessarily need a frame. if (NS_EVENT_NEEDS_FRAME(aEvent)) { NS_ASSERTION(mCurrentTarget, "mCurrentTarget is null. this should not happen. see bug #13007"); if (!mCurrentTarget) return NS_ERROR_NULL_POINTER; } if (mCurrentTarget) SetFrameExternalReference(mCurrentTarget); *aStatus = nsEventStatus_eIgnore; if (!aEvent) { NS_ERROR("aEvent is null. This should never happen."); return NS_ERROR_NULL_POINTER; } switch (aEvent->message) { case NS_MOUSE_LEFT_BUTTON_DOWN: #ifndef XP_OS2 BeginTrackingDragGesture ( aPresContext, (nsMouseEvent*)aEvent, aTargetFrame ); #endif mLClickCount = ((nsMouseEvent*)aEvent)->clickCount; SetClickCount(aPresContext, (nsMouseEvent*)aEvent, aStatus); mNormalLMouseEventInProcess = PR_TRUE; break; case NS_MOUSE_MIDDLE_BUTTON_DOWN: mMClickCount = ((nsMouseEvent*)aEvent)->clickCount; SetClickCount(aPresContext, (nsMouseEvent*)aEvent, aStatus); break; case NS_MOUSE_RIGHT_BUTTON_DOWN: #ifdef XP_OS2 BeginTrackingDragGesture ( aPresContext, (nsMouseEvent*)aEvent, aTargetFrame ); #endif mRClickCount = ((nsMouseEvent*)aEvent)->clickCount; SetClickCount(aPresContext, (nsMouseEvent*)aEvent, aStatus); break; case NS_MOUSE_LEFT_BUTTON_UP: #ifdef CLICK_HOLD_CONTEXT_MENUS KillClickHoldTimer(); #endif #ifndef XP_OS2 StopTrackingDragGesture(); #endif mNormalLMouseEventInProcess = PR_FALSE; case NS_MOUSE_RIGHT_BUTTON_UP: #ifdef XP_OS2 StopTrackingDragGesture(); #endif case NS_MOUSE_MIDDLE_BUTTON_UP: SetClickCount(aPresContext, (nsMouseEvent*)aEvent, aStatus); break; case NS_MOUSE_EXIT: // If the event coordinate is within the bounds of the view, // and this is not the top-level window, then it's not really // an exit --- we may have traversed widget boundaries but // we're still in our toplevel window. // On the other hand, if we exit a toplevel window, then // it's really an exit even if the mouse is still in the // window bounds --- the mouse probably moved into some // "on top" window. { nsMouseEvent* mouseEvent = NS_STATIC_CAST(nsMouseEvent*, aEvent); nsCOMPtr parentWidget = getter_AddRefs(mouseEvent->widget->GetParent()); if (parentWidget && (aView->GetBounds() - aView->GetPosition()).Contains(aEvent->point)) { // treat it as a move so we don't generate spurious "exit" // events Any necessary exit events will be generated by // GenerateMouseEnterExit aEvent->message = NS_MOUSE_MOVE; // then fall through... } else { GenerateMouseEnterExit((nsGUIEvent*)aEvent); //This is a window level mouse exit event and should stop here aEvent->message = 0; break; } } case NS_MOUSE_MOVE: // on the Mac, GenerateDragGesture() may not return until the drag // has completed and so |aTargetFrame| may have been deleted (moving // a bookmark, for example). If this is the case, however, we know // that ClearFrameRefs() has been called and it cleared out // |mCurrentTarget|. As a result, we should pass |mCurrentTarget| // into UpdateCursor(). GenerateDragGesture(aPresContext, (nsMouseEvent*)aEvent); UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus); GenerateMouseEnterExit((nsGUIEvent*)aEvent); break; #ifdef CLICK_HOLD_CONTEXT_MENUS case NS_DRAGDROP_GESTURE: // an external drag gesture event came in, not generated internally // by Gecko. Make sure we get rid of the click-hold timer. KillClickHoldTimer(); break; #endif case NS_DRAGDROP_OVER: GenerateDragDropEnterExit(aPresContext, (nsGUIEvent*)aEvent); break; case NS_GOTFOCUS: { // This is called when a child widget has received focus. // We need to take care of sending a blur event for the previously // focused content and document, then dispatching a focus // event to the target content, its document, and its window. EnsureDocument(aPresContext); // If the document didn't change, then the only thing that could have // changed is the focused content node. That's handled elsewhere // (SetContentState and SendFocusBlur). if (gLastFocusedDocument == mDocument) break; if (mDocument) { if (gLastFocusedDocument && gLastFocusedPresContext) { nsCOMPtr ourWindow = do_QueryInterface(GetDocumentOuterWindow(gLastFocusedDocument)); // If the focus controller is already suppressed, it means that we // are in the middle of an activate sequence. In this case, we do // _not_ want to fire a blur on the previously focused content, since // we will be focusing it again later when we receive the NS_ACTIVATE // event. See bug 120209. // Hold a strong ref to the focus controller, since we need // it after event dispatch. nsCOMPtr focusController; PRBool isAlreadySuppressed = PR_FALSE; if (ourWindow) { focusController = ourWindow->GetRootFocusController(); if (focusController) { focusController->GetSuppressFocus(&isAlreadySuppressed); focusController->SetSuppressFocus(PR_TRUE, "NS_GOTFOCUS ESM Suppression"); } } if (!isAlreadySuppressed) { // Fire the blur event on the previously focused document. nsEventStatus blurstatus = nsEventStatus_eIgnore; nsEvent blurevent(PR_TRUE, NS_BLUR_CONTENT); gLastFocusedDocument->HandleDOMEvent(gLastFocusedPresContext, &blurevent, nsnull, NS_EVENT_FLAG_INIT, &blurstatus); if (!mCurrentFocus && gLastFocusedContent) { // We also need to blur the previously focused content node here, // if we don't have a focused content node in this document. // (SendFocusBlur isn't called in this case). nsCOMPtr blurContent = gLastFocusedContent; gLastFocusedContent->HandleDOMEvent(gLastFocusedPresContext, &blurevent, nsnull, NS_EVENT_FLAG_INIT, &blurstatus); // XXX bryner this isn't quite right -- it can result in // firing two blur events on the content. nsCOMPtr doc; if (gLastFocusedContent) // could have changed in HandleDOMEvent doc = gLastFocusedContent->GetDocument(); if (doc) { nsIPresShell *shell = doc->GetShellAt(0); if (shell) { nsCOMPtr oldPresContext = shell->GetPresContext(); nsCOMPtr esm; esm = oldPresContext->EventStateManager(); esm->SetFocusedContent(gLastFocusedContent); gLastFocusedContent->HandleDOMEvent(oldPresContext, &blurevent, nsnull, NS_EVENT_FLAG_INIT, &blurstatus); esm->SetFocusedContent(nsnull); NS_IF_RELEASE(gLastFocusedContent); } } } } if (focusController) focusController->SetSuppressFocus(PR_FALSE, "NS_GOTFOCUS ESM Suppression"); } // Now we should fire the focus event. We fire it on the document, // then the content node, then the window. nsCOMPtr globalObject = GetDocumentOuterWindow(mDocument); if (globalObject) { // We don't want there to be a focused content node while we're // dispatching the focus event. nsCOMPtr currentFocus = mCurrentFocus; // "leak" this reference, but we take it back later SetFocusedContent(nsnull); nsEventStatus status = nsEventStatus_eIgnore; nsEvent focusevent(PR_TRUE, NS_FOCUS_CONTENT); if (gLastFocusedDocument != mDocument) { mDocument->HandleDOMEvent(aPresContext, &focusevent, nsnull, NS_EVENT_FLAG_INIT, &status); if (currentFocus && currentFocus != gLastFocusedContent) currentFocus->HandleDOMEvent(aPresContext, &focusevent, nsnull, NS_EVENT_FLAG_INIT, &status); } globalObject->HandleDOMEvent(aPresContext, &focusevent, nsnull, NS_EVENT_FLAG_INIT, &status); SetFocusedContent(currentFocus); // we kept this reference above NS_IF_RELEASE(gLastFocusedContent); gLastFocusedContent = mCurrentFocus; NS_IF_ADDREF(gLastFocusedContent); } // Try to keep the focus controllers and the globals in synch if (gLastFocusedDocument && gLastFocusedDocument != mDocument) { nsIFocusController *lastController = nsnull; nsCOMPtr lastWindow = do_QueryInterface(GetDocumentOuterWindow(gLastFocusedDocument)); if (lastWindow) lastController = lastWindow->GetRootFocusController(); nsIFocusController *nextController = nsnull; nsCOMPtr nextWindow = do_QueryInterface(GetDocumentOuterWindow(mDocument)); if (nextWindow) nextController = nextWindow->GetRootFocusController(); if (lastController != nextController && lastController && nextController) lastController->SetActive(PR_FALSE); } NS_IF_RELEASE(gLastFocusedDocument); gLastFocusedDocument = mDocument; gLastFocusedPresContext = aPresContext; NS_IF_ADDREF(gLastFocusedDocument); } ResetBrowseWithCaret(); } break; case NS_LOSTFOCUS: { // Hide the caret used in "browse with caret mode" if (mBrowseWithCaret && mPresContext) { nsIPresShell *presShell = mPresContext->GetPresShell(); if (presShell) SetContentCaretVisible(presShell, mCurrentFocus, PR_FALSE); } // If focus is going to another mozilla window, we wait for the // focus event and fire a blur on the old focused content at that time. // This allows "-moz-user-focus: ignore" to work. #if defined(XP_WIN) || defined(XP_OS2) if (!NS_STATIC_CAST(nsFocusEvent*, aEvent)->isMozWindowTakingFocus) { // This situation occurs when focus goes to a non-gecko child window // in an embedding application. In this case we do fire a blur // immediately. EnsureDocument(aPresContext); // We can get a deactivate on an Ender (editor) widget. In this // case, we would like to obtain the DOM Window to start // with by looking at gLastFocusedContent. nsCOMPtr ourGlobal; if (gLastFocusedContent) { nsIDocument* doc = gLastFocusedContent->GetDocument(); if (doc) { ourGlobal = GetDocumentOuterWindow(doc); } else { ourGlobal = GetDocumentOuterWindow(mDocument); NS_RELEASE(gLastFocusedContent); } } else { ourGlobal = GetDocumentOuterWindow(mDocument); } // Now fire blurs. We fire a blur on the focused document, element, // and window. nsEventStatus status = nsEventStatus_eIgnore; nsEvent event(PR_TRUE, NS_BLUR_CONTENT); if (gLastFocusedDocument && gLastFocusedPresContext) { if (gLastFocusedContent) { // Retrieve this content node's pres context. it can be out of sync // in the Ender widget case. nsCOMPtr doc = gLastFocusedContent->GetDocument(); if (doc) { nsIPresShell *shell = doc->GetShellAt(0); if (shell) { nsCOMPtr oldPresContext = shell->GetPresContext(); nsCOMPtr esm = oldPresContext->EventStateManager(); esm->SetFocusedContent(gLastFocusedContent); gLastFocusedContent->HandleDOMEvent(oldPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status); esm->SetFocusedContent(nsnull); NS_IF_RELEASE(gLastFocusedContent); } } } // fire blur on document and window if (gLastFocusedDocument) { // get the window here, in case the event causes // gLastFocusedDocument to change. nsCOMPtr globalObject = GetDocumentOuterWindow(gLastFocusedDocument); gLastFocusedDocument->HandleDOMEvent(gLastFocusedPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status); if (globalObject) globalObject->HandleDOMEvent(gLastFocusedPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status); } // Now clear our our global variables mCurrentTarget = nsnull; NS_IF_RELEASE(gLastFocusedDocument); gLastFocusedPresContext = nsnull; } } #endif } break; case NS_ACTIVATE: { // If we have a focus controller, and if it has a focused window and a // focused element in its focus memory, then restore the focus to those // objects. EnsureDocument(aPresContext); nsCOMPtr win = do_QueryInterface(GetDocumentOuterWindow(mDocument)); if (!win) { NS_ERROR("win is null. this happens [often on xlib builds]. see bug #79213"); return NS_ERROR_NULL_POINTER; } // Hold a strong ref to the focus controller, since we need // it after event dispatch. nsCOMPtr focusController = win->GetRootFocusController(); nsCOMPtr focusedElement; nsCOMPtr focusedWindow; if (focusController) { // Obtain focus info from the focus controller. focusController->GetFocusedWindow(getter_AddRefs(focusedWindow)); focusController->GetFocusedElement(getter_AddRefs(focusedElement)); focusController->SetSuppressFocusScroll(PR_TRUE); focusController->SetActive(PR_TRUE); } if (!focusedWindow) focusedWindow = win; NS_WARN_IF_FALSE(focusedWindow,"check why focusedWindow is null!!!"); // Focus the DOM window. if (focusedWindow) { focusedWindow->Focus(); nsCOMPtr document = GetDocumentFromWindow(focusedWindow); if (document) { // Use a strong ref to make sure that the shell is alive still // when calling FrameSelection(). nsCOMPtr shell = document->GetShellAt(0); NS_ASSERTION(shell, "Focus events should not be getting thru when this is null!"); if (shell) { if (focusedElement) { nsCOMPtr focusContent = do_QueryInterface(focusedElement); nsCOMPtr context = shell->GetPresContext(); focusContent->SetFocus(context); } // disable selection mousedown state on activation shell->FrameSelection()->SetMouseDownState(PR_FALSE); } } } if (focusController) { // Make sure the focus controller is up-to-date, since restoring // focus memory may have caused focus to go elsewhere. if (gLastFocusedDocument && gLastFocusedDocument == mDocument) { nsCOMPtr focusElement = do_QueryInterface(mCurrentFocus); focusController->SetFocusedElement(focusElement); } PRBool isSuppressed; focusController->GetSuppressFocus(&isSuppressed); while (isSuppressed) { // Unsuppress and let the focus controller listen again. focusController->SetSuppressFocus(PR_FALSE, "Activation Suppression"); focusController->GetSuppressFocus(&isSuppressed); } focusController->SetSuppressFocusScroll(PR_FALSE); } } break; case NS_DEACTIVATE: { EnsureDocument(aPresContext); nsCOMPtr ourGlobal = GetDocumentOuterWindow(mDocument); // Suppress the focus controller for the duration of the // de-activation. This will cause it to remember the last // focused sub-window and sub-element for this top-level // window. nsCOMPtr focusController = GetFocusControllerForDocument(mDocument); if (focusController) focusController->SetSuppressFocus(PR_TRUE, "Deactivate Suppression"); // Now fire blurs. Blur the content, then the document, then the window. if (gLastFocusedDocument && gLastFocusedDocument == mDocument) { nsEventStatus status = nsEventStatus_eIgnore; nsEvent event(PR_TRUE, NS_BLUR_CONTENT); if (gLastFocusedContent) { nsIPresShell *shell = gLastFocusedDocument->GetShellAt(0); if (shell) { nsCOMPtr oldPresContext = shell->GetPresContext(); nsCOMPtr focusedElement; if (focusController) focusController->GetFocusedElement(getter_AddRefs(focusedElement)); nsCOMPtr esm; esm = oldPresContext->EventStateManager(); esm->SetFocusedContent(gLastFocusedContent); nsCOMPtr focusedContent = do_QueryInterface(focusedElement); if (focusedContent) { // Blur the element. focusedContent->HandleDOMEvent(oldPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status); } esm->SetFocusedContent(nsnull); NS_IF_RELEASE(gLastFocusedContent); } } // fire blur on document and window mDocument->HandleDOMEvent(aPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status); if (ourGlobal) ourGlobal->HandleDOMEvent(aPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, &status); // Now clear our our global variables mCurrentTarget = nsnull; NS_IF_RELEASE(gLastFocusedDocument); gLastFocusedPresContext = nsnull; } if (focusController) { focusController->SetActive(PR_FALSE); focusController->SetSuppressFocus(PR_FALSE, "Deactivate Suppression"); } } break; case NS_KEY_PRESS: { nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; PRInt32 modifierMask = 0; if (keyEvent->isShift) modifierMask |= NS_MODIFIER_SHIFT; if (keyEvent->isControl) modifierMask |= NS_MODIFIER_CONTROL; if (keyEvent->isAlt) modifierMask |= NS_MODIFIER_ALT; if (keyEvent->isMeta) modifierMask |= NS_MODIFIER_META; // Prevent keyboard scrolling while an accesskey modifier is in use. if (modifierMask && (modifierMask == sChromeAccessModifier || modifierMask == sContentAccessModifier)) HandleAccessKey(aPresContext, keyEvent, aStatus, -1, eAccessKeyProcessingNormal, modifierMask); } case NS_KEY_DOWN: case NS_KEY_UP: case NS_MOUSE_SCROLL: { if (mCurrentFocus) { mCurrentTargetContent = mCurrentFocus; } } break; } return NS_OK; } static PRInt32 GetAccessModifierMask(nsISupports* aDocShell) { nsCOMPtr treeItem(do_QueryInterface(aDocShell)); if (!treeItem) return -1; // invalid modifier PRInt32 itemType; treeItem->GetItemType(&itemType); switch (itemType) { case nsIDocShellTreeItem::typeChrome: return sChromeAccessModifier; case nsIDocShellTreeItem::typeContent: return sContentAccessModifier; default: return -1; // invalid modifier } } // Note: for the in parameter aChildOffset, // -1 stands for not bubbling from the child docShell // 0 -- childCount - 1 stands for the child docShell's offset // which bubbles up the access key handling void nsEventStateManager::HandleAccessKey(nsPresContext* aPresContext, nsKeyEvent *aEvent, nsEventStatus* aStatus, PRInt32 aChildOffset, ProcessingAccessKeyState aAccessKeyState, PRInt32 aModifierMask) { nsCOMPtr pcContainer = aPresContext->GetContainer(); NS_ASSERTION(pcContainer, "no container for presContext"); // Alt or other accesskey modifier is down, we may need to do an accesskey if (mAccessKeys && aModifierMask == GetAccessModifierMask(pcContainer)) { // Someone registered an accesskey. Find and activate it. PRUint32 accKey = (IS_IN_BMP(aEvent->charCode)) ? ToLowerCase((PRUnichar)aEvent->charCode) : aEvent->charCode; nsVoidKey key(NS_INT32_TO_PTR(accKey)); if (mAccessKeys->Exists(&key)) { nsCOMPtr content = dont_AddRef(NS_STATIC_CAST(nsIContent*, mAccessKeys->Get(&key))); // if it's a XUL element... if (content->IsContentOfType(nsIContent::eXUL)) { // find out what type of content node this is if (content->Tag() == nsXULAtoms::label) { // If anything fails, this will be null ... nsCOMPtr element; nsAutoString control; content->GetAttr(kNameSpaceID_None, nsXULAtoms::control, control); if (!control.IsEmpty()) { nsCOMPtr domDocument = do_QueryInterface(content->GetDocument()); if (domDocument) domDocument->GetElementById(control, getter_AddRefs(element)); } // ... that here we'll either change |content| to the element // referenced by |element|, or clear it. content = do_QueryInterface(element); } if (!content) return; nsIFrame* frame = nsnull; aPresContext->PresShell()->GetPrimaryFrameFor(content, &frame); if (frame) { const nsStyleVisibility* vis = frame->GetStyleVisibility(); PRBool viewShown = frame->AreAncestorViewsVisible(); // get the XUL element nsCOMPtr element = do_QueryInterface(content); // if collapsed or hidden, we don't get tabbed into. if (viewShown && vis->mVisible != NS_STYLE_VISIBILITY_COLLAPSE && vis->mVisible != NS_STYLE_VISIBILITY_HIDDEN && element) { // find out what type of content node this is nsIAtom *atom = content->Tag(); // define behavior for each type of XUL element: if (atom == nsXULAtoms::textbox || atom == nsXULAtoms::menulist) { // if it's a text box or menulist, give it focus element->Focus(); } else if (atom == nsXULAtoms::toolbarbutton) { // if it's a toolbar button, just click element->Click(); } else { // otherwise, focus and click in it element->Focus(); element->Click(); } } } } else { // otherwise, it must be HTML // It's hard to say what HTML4 wants us to do in all cases. // So for now we'll settle for A) Set focus (except for