Bug 464858 - intermittent / recurring fail of test_db_update_v1.js. bustagefix.
[wine-gecko.git] / dom / src / base / nsFocusController.cpp
blob4535e8fa0afcee8c622f91713f523ca6f3dc35c9
1 /* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is the Mozilla browser.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications, Inc.
20 * Portions created by the Initial Developer are Copyright (C) 1999
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * David W. Hyatt <hyatt@netscape.com> (Original Author)
25 * Dan Rosen <dr@netscape.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #include "nsIContent.h"
42 #include "nsIControllers.h"
43 #include "nsIDOMDocument.h"
44 #include "nsIDOMHTMLDocument.h"
45 #include "nsIDOMElement.h"
46 #include "nsIDOMNSHTMLInputElement.h"
47 #include "nsIDOMNSHTMLTextAreaElement.h"
48 #include "nsIDOMNSEvent.h"
49 #include "nsIDOMWindowInternal.h"
50 #include "nsIDocument.h"
51 #include "nsPresContext.h"
52 #include "nsIPresShell.h"
53 #include "nsIScriptGlobalObject.h"
54 #include "nsPIDOMWindow.h"
55 #include "nsFocusController.h"
56 #include "prlog.h"
57 #include "nsIDOMEventTarget.h"
58 #include "nsIEventStateManager.h"
59 #include "nsIDocShell.h"
60 #include "nsIBaseWindow.h"
61 #include "nsIWindowWatcher.h"
62 #include "nsIDocShellTreeItem.h"
63 #include "nsIDocShellTreeOwner.h"
64 #include "nsIInterfaceRequestorUtils.h"
65 #include "nsServiceManagerUtils.h"
66 #include "nsGlobalWindow.h"
68 #ifdef MOZ_XUL
69 #include "nsIDOMXULDocument.h"
70 #include "nsIDOMXULElement.h"
71 #endif
73 ////////////////////////////////////////////////////////////////////////
75 nsFocusController::nsFocusController(void)
76 : mSuppressFocus(0),
77 mSuppressFocusScroll(PR_FALSE),
78 mActive(PR_FALSE),
79 mUpdateWindowWatcher(PR_FALSE),
80 mNeedUpdateCommands(PR_FALSE)
84 nsFocusController::~nsFocusController(void)
88 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFocusController)
90 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsFocusController, nsIFocusController)
91 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsFocusController,
92 nsIFocusController)
94 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusController)
95 NS_INTERFACE_MAP_ENTRY(nsIFocusController)
96 NS_INTERFACE_MAP_ENTRY(nsIDOMFocusListener)
97 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
98 NS_INTERFACE_MAP_ENTRY(nsSupportsWeakReference)
99 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusController)
100 NS_INTERFACE_MAP_END
102 NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsFocusController)
103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFocusController)
104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCurrentElement)
105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPreviousElement)
106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCurrentWindow)
107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPreviousWindow)
108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPopupNode)
109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
111 NS_IMETHODIMP
112 nsFocusController::Create(nsIFocusController** aResult)
114 nsFocusController* controller = new nsFocusController();
115 if (!controller)
116 return NS_ERROR_OUT_OF_MEMORY;
118 *aResult = controller;
119 NS_ADDREF(*aResult);
120 return NS_OK;
124 ////////////////////////////////////////////////////////////////
125 // nsIFocusController Interface
127 NS_IMETHODIMP
128 nsFocusController::GetFocusedElement(nsIDOMElement** aElement)
130 *aElement = mCurrentElement;
131 NS_IF_ADDREF(*aElement);
132 return NS_OK;
135 NS_IMETHODIMP
136 nsFocusController::GetFocusedWindow(nsIDOMWindowInternal** aWindow)
138 *aWindow = mCurrentWindow;
139 NS_IF_ADDREF(*aWindow);
140 return NS_OK;
143 NS_IMETHODIMP
144 nsFocusController::SetFocusedElement(nsIDOMElement* aElement)
146 if (mCurrentElement)
147 mPreviousElement = mCurrentElement;
148 else if (aElement)
149 mPreviousElement = aElement;
151 mNeedUpdateCommands = mNeedUpdateCommands || mCurrentElement != aElement;
152 mCurrentElement = aElement;
154 if (!mSuppressFocus) {
155 // Need to update focus commands when focus switches from
156 // an element to no element, so don't test mCurrentElement
157 // before updating.
158 UpdateCommands();
160 return NS_OK;
163 NS_IMETHODIMP
164 nsFocusController::RewindFocusState()
166 mCurrentElement = mPreviousElement;
167 mCurrentWindow = mPreviousWindow;
169 return NS_OK;
172 NS_IMETHODIMP
173 nsFocusController::SetFocusedWindow(nsIDOMWindowInternal* aWindow)
175 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aWindow);
177 if (win) {
178 win = win->GetOuterWindow();
181 NS_ASSERTION(!win || !win->IsInnerWindow(),
182 "Uh, inner window can't have focus!");
184 if (win && (mCurrentWindow != win)) {
185 nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(win->GetDocShell());
186 if (basewin)
187 basewin->SetFocus();
190 if (mCurrentWindow) {
191 mPreviousWindow = mCurrentWindow;
192 } else if (win) {
193 mPreviousWindow = win;
196 mNeedUpdateCommands = mNeedUpdateCommands || mCurrentWindow != win;
197 mCurrentWindow = win;
199 if (mUpdateWindowWatcher) {
200 NS_ASSERTION(mActive, "This shouldn't happen");
201 if (mCurrentWindow)
202 UpdateWWActiveWindow();
203 mUpdateWindowWatcher = PR_FALSE;
206 return NS_OK;
210 void
211 nsFocusController::UpdateCommands()
213 if (!mNeedUpdateCommands) {
214 return;
216 nsCOMPtr<nsIDOMWindowInternal> window;
217 nsCOMPtr<nsIDocument> doc;
218 if (mCurrentWindow) {
219 window = mCurrentWindow;
220 nsCOMPtr<nsIDOMWindow> domWin(do_QueryInterface(window));
221 nsCOMPtr<nsIDOMDocument> domDoc;
222 domWin->GetDocument(getter_AddRefs(domDoc));
223 doc = do_QueryInterface(domDoc);
225 else if (mCurrentElement) {
226 nsCOMPtr<nsIDOMDocument> domDoc;
227 mCurrentElement->GetOwnerDocument(getter_AddRefs(domDoc));
228 if (domDoc) {
229 doc = do_QueryInterface(domDoc);
230 window = do_QueryInterface(doc->GetScriptGlobalObject());
234 // If there is no presshell, it's a zombie document which can't handle the command updates
235 if (window && doc && doc->GetPrimaryShell()) {
236 // Not a zombie document, so we can handle the command update
237 window->UpdateCommands(NS_LITERAL_STRING("focus"));
238 mNeedUpdateCommands = PR_FALSE;
243 NS_IMETHODIMP
244 nsFocusController::GetControllers(nsIControllers** aResult)
246 // XXX: we should fix this so there's a generic interface that
247 // describes controllers, so this code would have no special
248 // knowledge of what object might have controllers.
249 if (mCurrentElement) {
251 #ifdef MOZ_XUL
252 nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(mCurrentElement));
253 if (xulElement)
254 return xulElement->GetControllers(aResult);
255 #endif
257 nsCOMPtr<nsIDOMNSHTMLTextAreaElement> htmlTextArea =
258 do_QueryInterface(mCurrentElement);
259 if (htmlTextArea)
260 return htmlTextArea->GetControllers(aResult);
262 nsCOMPtr<nsIDOMNSHTMLInputElement> htmlInputElement =
263 do_QueryInterface(mCurrentElement);
264 if (htmlInputElement)
265 return htmlInputElement->GetControllers(aResult);
267 nsCOMPtr<nsIContent> content = do_QueryInterface(mCurrentElement);
268 if (content && content->IsEditable()) {
269 // Move up to the window.
270 nsCOMPtr<nsIDOMDocument> domDoc;
271 mCurrentElement->GetOwnerDocument(getter_AddRefs(domDoc));
272 nsCOMPtr<nsIDOMWindowInternal> domWindow =
273 do_QueryInterface(GetWindowFromDocument(domDoc));
274 if (domWindow)
275 return domWindow->GetControllers(aResult);
278 else if (mCurrentWindow) {
279 nsCOMPtr<nsIDOMWindowInternal> domWindow =
280 do_QueryInterface(mCurrentWindow);
281 if (domWindow)
282 return domWindow->GetControllers(aResult);
285 *aResult = nsnull;
286 return NS_OK;
289 NS_IMETHODIMP
290 nsFocusController::MoveFocus(PRBool aForward, nsIDOMElement* aElt)
292 // Obtain the doc that we'll be shifting focus inside.
293 nsCOMPtr<nsIDocument> doc;
294 nsCOMPtr<nsIContent> content;
295 if (aElt) {
296 content = do_QueryInterface(aElt);
297 doc = content->GetDocument();
299 else {
300 if (mCurrentElement) {
301 content = do_QueryInterface(mCurrentElement);
302 doc = content->GetDocument();
303 content = nsnull;
305 else if (mCurrentWindow) {
306 nsCOMPtr<nsIDOMDocument> domDoc;
307 mCurrentWindow->GetDocument(getter_AddRefs(domDoc));
308 doc = do_QueryInterface(domDoc);
312 if (!doc) {
313 // No way to obtain an event state manager. Give up.
314 return NS_ERROR_FAILURE;
317 nsIPresShell *shell = doc->GetPrimaryShell();
318 if (!shell)
319 return NS_ERROR_FAILURE;
321 // Make sure frames have been constructed before shifting focus, bug 273092.
322 shell->FlushPendingNotifications(Flush_Frames);
324 // Retrieve the context
325 nsCOMPtr<nsPresContext> presContext = shell->GetPresContext();
327 // Make this ESM shift the focus per our instructions.
328 return presContext->EventStateManager()->ShiftFocus(aForward, content);
331 /////
332 // nsIDOMFocusListener
333 /////
335 nsresult
336 nsFocusController::Focus(nsIDOMEvent* aEvent)
338 if (mSuppressFocus)
339 return NS_OK;
341 nsCOMPtr<nsIDOMEventTarget> t;
343 nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aEvent));
344 if (nsevent) {
345 nsevent->GetOriginalTarget(getter_AddRefs(t));
348 nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(t);
349 if (domElement && (domElement != mCurrentElement)) {
350 SetFocusedElement(domElement);
352 // Also set focus to our innermost window.
353 // XXX Must be done for the Ender case, since ender causes a blur,
354 // but we don't hear the subsequent focus to the Ender window.
355 nsCOMPtr<nsIDOMDocument> ownerDoc;
356 domElement->GetOwnerDocument(getter_AddRefs(ownerDoc));
357 nsCOMPtr<nsIDOMWindowInternal> domWindow = GetWindowFromDocument(ownerDoc);
358 if (domWindow)
359 SetFocusedWindow(domWindow);
361 else {
362 // We're focusing a window. We only want to do an update commands
363 // if no element is focused.
364 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(t);
365 if (domDoc) {
366 nsCOMPtr<nsIDOMWindowInternal> domWindow = GetWindowFromDocument(domDoc);
367 if (domWindow) {
368 SetFocusedWindow(domWindow);
369 if (mCurrentElement) {
370 // Make sure this element is in our window. If not, we
371 // should clear this field.
372 nsCOMPtr<nsIDOMDocument> ownerDoc;
373 mCurrentElement->GetOwnerDocument(getter_AddRefs(ownerDoc));
374 nsCOMPtr<nsIDOMDocument> windowDoc;
375 mCurrentWindow->GetDocument(getter_AddRefs(windowDoc));
376 if (ownerDoc != windowDoc)
377 mCurrentElement = mPreviousElement = nsnull;
379 else
380 mPreviousElement = nsnull;
382 if (!mCurrentElement) {
383 UpdateCommands();
389 return NS_OK;
392 nsresult
393 nsFocusController::Blur(nsIDOMEvent* aEvent)
395 if (mSuppressFocus)
396 return NS_OK;
398 nsCOMPtr<nsIDOMEventTarget> t;
400 nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aEvent));
402 if (nsevent) {
403 nsevent->GetOriginalTarget(getter_AddRefs(t));
406 nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(t);
407 if (domElement) {
408 SetFocusedElement(nsnull);
411 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(t);
412 if (domDoc) {
413 nsCOMPtr<nsIDOMWindowInternal> domWindow = GetWindowFromDocument(domDoc);
414 if (domWindow)
415 SetFocusedWindow(nsnull);
418 return NS_OK;
421 nsPIDOMWindow *
422 nsFocusController::GetWindowFromDocument(nsIDOMDocument* aDocument)
424 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
425 if (!doc)
426 return nsnull;
428 return doc->GetWindow();
431 NS_IMETHODIMP
432 nsFocusController::GetControllerForCommand(const char * aCommand,
433 nsIController** _retval)
435 NS_ENSURE_ARG_POINTER(_retval);
436 *_retval = nsnull;
438 nsCOMPtr<nsIControllers> controllers;
439 nsCOMPtr<nsIController> controller;
441 GetControllers(getter_AddRefs(controllers));
442 if(controllers) {
443 controllers->GetControllerForCommand(aCommand, getter_AddRefs(controller));
444 if(controller) {
445 controller.swap(*_retval);
446 return NS_OK;
450 nsCOMPtr<nsPIDOMWindow> currentWindow;
451 if (mCurrentElement) {
452 // Move up to the window.
453 nsCOMPtr<nsIDOMDocument> domDoc;
454 mCurrentElement->GetOwnerDocument(getter_AddRefs(domDoc));
455 currentWindow = do_QueryInterface(GetWindowFromDocument(domDoc));
457 else if (mCurrentWindow) {
458 nsGlobalWindow *win =
459 static_cast<nsGlobalWindow *>
460 (static_cast<nsIDOMWindowInternal *>(mCurrentWindow));
461 currentWindow = win->GetPrivateParent();
463 else return NS_OK;
465 while(currentWindow) {
466 nsCOMPtr<nsIDOMWindowInternal> domWindow(do_QueryInterface(currentWindow));
468 nsCOMPtr<nsIControllers> controllers2;
469 domWindow->GetControllers(getter_AddRefs(controllers2));
470 if(controllers2) {
471 controllers2->GetControllerForCommand(aCommand,
472 getter_AddRefs(controller));
473 if(controller) {
474 controller.swap(*_retval);
475 return NS_OK;
479 nsGlobalWindow *win =
480 static_cast<nsGlobalWindow *>
481 (static_cast<nsIDOMWindowInternal *>(currentWindow));
482 currentWindow = win->GetPrivateParent();
485 return NS_OK;
488 NS_IMETHODIMP
489 nsFocusController::GetSuppressFocusScroll(PRBool* aSuppressFocusScroll)
491 *aSuppressFocusScroll = mSuppressFocusScroll;
492 return NS_OK;
495 NS_IMETHODIMP
496 nsFocusController::SetSuppressFocusScroll(PRBool aSuppressFocusScroll)
498 mSuppressFocusScroll = aSuppressFocusScroll;
499 return NS_OK;
502 NS_IMETHODIMP
503 nsFocusController::GetSuppressFocus(PRBool* aSuppressFocus)
505 *aSuppressFocus = (mSuppressFocus > 0);
506 return NS_OK;
509 NS_IMETHODIMP
510 nsFocusController::SetSuppressFocus(PRBool aSuppressFocus, const char* aReason)
512 if(aSuppressFocus) {
513 ++mSuppressFocus;
514 //#ifdef DEBUG_hyatt
515 //printf("[%p] SuppressFocus incremented to %d. The reason is %s.\n", this, mSuppressFocus, aReason);
516 //#endif
518 else if(mSuppressFocus > 0) {
519 --mSuppressFocus;
520 //#ifdef DEBUG_hyatt
521 //printf("[%p] SuppressFocus decremented to %d. The reason is %s.\n", this, mSuppressFocus, aReason);
522 //#endif
524 else
525 // It's ok to unsuppress even if no suppression is active (bug 112294)
526 return NS_OK;
528 // we are unsuppressing after activating, so update focus-related commands
529 // we need this to update command, including the case where there is no element
530 // because nsPresShell::UnsuppressPainting may have just now unsuppressed
531 // focus on the currently focused window
532 if (!mSuppressFocus) {
533 // Always update commands if we have a current element
534 // and mNeedUpdateCommands is true (checked in nsFC::UpdateCommands)
535 UpdateCommands();
538 return NS_OK;
541 NS_IMETHODIMP
542 nsFocusController::GetActive(PRBool* aActive)
544 *aActive = mActive;
545 return NS_OK;
548 NS_IMETHODIMP
549 nsFocusController::SetActive(PRBool aActive)
551 mActive = aActive;
553 // We may be activated before we ever have a focused window set.
554 // This happens on window creation, where the FocusController
555 // is activated just prior to setting the focused window.
556 // (see nsEventStateManager::PreHandleEvent/NS_ACTIVATE)
557 // If this is the case, we need to queue a notification of the
558 // WindowWatcher until SetFocusedWindow is called.
559 if (mActive) {
560 if (mCurrentWindow)
561 UpdateWWActiveWindow();
562 else
563 mUpdateWindowWatcher = PR_TRUE;
564 } else
565 mUpdateWindowWatcher = PR_FALSE;
567 return NS_OK;
570 NS_IMETHODIMP
571 nsFocusController::ResetElementFocus()
573 mCurrentElement = mPreviousElement = nsnull;
574 return NS_OK;
577 void
578 nsFocusController::UpdateWWActiveWindow()
580 // Inform the window watcher of the new active window.
581 nsCOMPtr<nsIWindowWatcher> wwatch = do_GetService("@mozilla.org/embedcomp/window-watcher;1");
582 if (!wwatch) return;
584 // This gets the toplevel DOMWindow
585 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem =
586 do_QueryInterface(mCurrentWindow->GetDocShell());
587 if (!docShellAsItem) return;
589 nsCOMPtr<nsIDocShellTreeItem> rootItem;
590 docShellAsItem->GetRootTreeItem(getter_AddRefs(rootItem));
591 NS_ASSERTION(rootItem, "Invalid docshell tree - no root!");
593 nsCOMPtr<nsIDOMWindow> domWin = do_GetInterface(rootItem);
594 wwatch->SetActiveWindow(domWin);
597 NS_IMETHODIMP
598 nsFocusController::GetPopupNode(nsIDOMNode** aNode)
600 #ifdef DEBUG_dr
601 printf("dr :: nsFocusController::GetPopupNode\n");
602 #endif
604 *aNode = mPopupNode;
605 NS_IF_ADDREF(*aNode);
606 return NS_OK;
609 NS_IMETHODIMP
610 nsFocusController::SetPopupNode(nsIDOMNode* aNode)
612 #ifdef DEBUG_dr
613 printf("dr :: nsFocusController::SetPopupNode\n");
614 #endif
616 mPopupNode = aNode;
617 return NS_OK;