dom/base/nsFocusManager.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:830418b91261
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "mozilla/dom/TabParent.h"
7
8 #include "nsFocusManager.h"
9
10 #include "nsIInterfaceRequestorUtils.h"
11 #include "nsGkAtoms.h"
12 #include "nsContentUtils.h"
13 #include "nsIDocument.h"
14 #include "nsIDOMWindow.h"
15 #include "nsPIDOMWindow.h"
16 #include "nsIDOMElement.h"
17 #include "nsIDOMDocument.h"
18 #include "nsIDOMRange.h"
19 #include "nsIHTMLDocument.h"
20 #include "nsIDocShell.h"
21 #include "nsIDocShellTreeOwner.h"
22 #include "nsLayoutUtils.h"
23 #include "nsIPresShell.h"
24 #include "nsFrameTraversal.h"
25 #include "nsIWebNavigation.h"
26 #include "nsCaret.h"
27 #include "nsIBaseWindow.h"
28 #include "nsViewManager.h"
29 #include "nsFrameSelection.h"
30 #include "mozilla/dom/Selection.h"
31 #include "nsXULPopupManager.h"
32 #include "nsIScriptObjectPrincipal.h"
33 #include "nsIPrincipal.h"
34 #include "nsIObserverService.h"
35 #include "nsIObjectFrame.h"
36 #include "nsBindingManager.h"
37 #include "nsStyleCoord.h"
38
39 #include "mozilla/ContentEvents.h"
40 #include "mozilla/dom/Element.h"
41 #include "mozilla/EventDispatcher.h"
42 #include "mozilla/EventStateManager.h"
43 #include "mozilla/EventStates.h"
44 #include "mozilla/IMEStateManager.h"
45 #include "mozilla/LookAndFeel.h"
46 #include "mozilla/Preferences.h"
47 #include "mozilla/Services.h"
48 #include <algorithm>
49
50 #ifdef MOZ_XUL
51 #include "nsIDOMXULTextboxElement.h"
52 #include "nsIDOMXULMenuListElement.h"
53 #endif
54
55 #ifdef ACCESSIBILITY
56 #include "nsAccessibilityService.h"
57 #endif
58
59 #ifndef XP_MACOSX
60 #include "nsIScriptError.h"
61 #endif
62
63 using namespace mozilla;
64 using namespace mozilla::dom;
65 using namespace mozilla::widget;
66
67 #ifdef PR_LOGGING
68
69 // Two types of focus pr logging are available:
70 // 'Focus' for normal focus manager calls
71 // 'FocusNavigation' for tab and document navigation
72 PRLogModuleInfo* gFocusLog;
73 PRLogModuleInfo* gFocusNavigationLog;
74
75 #define LOGFOCUS(args) PR_LOG(gFocusLog, 4, args)
76 #define LOGFOCUSNAVIGATION(args) PR_LOG(gFocusNavigationLog, 4, args)
77
78 #define LOGTAG(log, format, content) \
79 { \
80 nsAutoCString tag(NS_LITERAL_CSTRING("(none)")); \
81 if (content) { \
82 content->Tag()->ToUTF8String(tag); \
83 } \
84 PR_LOG(log, 4, (format, tag.get())); \
85 }
86
87 #define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
88 #define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content)
89
90 #else
91
92 #define LOGFOCUS(args)
93 #define LOGFOCUSNAVIGATION(args)
94 #define LOGCONTENT(format, content)
95 #define LOGCONTENTNAVIGATION(format, content)
96
97 #endif
98
99 struct nsDelayedBlurOrFocusEvent
100 {
101 nsDelayedBlurOrFocusEvent(uint32_t aType,
102 nsIPresShell* aPresShell,
103 nsIDocument* aDocument,
104 EventTarget* aTarget)
105 : mType(aType),
106 mPresShell(aPresShell),
107 mDocument(aDocument),
108 mTarget(aTarget) { }
109
110 nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
111 : mType(aOther.mType),
112 mPresShell(aOther.mPresShell),
113 mDocument(aOther.mDocument),
114 mTarget(aOther.mTarget) { }
115
116 uint32_t mType;
117 nsCOMPtr<nsIPresShell> mPresShell;
118 nsCOMPtr<nsIDocument> mDocument;
119 nsCOMPtr<EventTarget> mTarget;
120 };
121
122 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
123 NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
124 NS_INTERFACE_MAP_ENTRY(nsIObserver)
125 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
126 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
127 NS_INTERFACE_MAP_END
128
129 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
130 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
131
132 NS_IMPL_CYCLE_COLLECTION(nsFocusManager,
133 mActiveWindow,
134 mFocusedWindow,
135 mFocusedContent,
136 mFirstBlurEvent,
137 mFirstFocusEvent,
138 mWindowBeingLowered)
139
140 nsFocusManager* nsFocusManager::sInstance = nullptr;
141 bool nsFocusManager::sMouseFocusesFormControl = false;
142 bool nsFocusManager::sTestMode = false;
143
144 static const char* kObservedPrefs[] = {
145 "accessibility.browsewithcaret",
146 "accessibility.tabfocus_applies_to_xul",
147 "accessibility.mouse_focuses_formcontrol",
148 "focusmanager.testmode",
149 nullptr
150 };
151
152 nsFocusManager::nsFocusManager()
153 { }
154
155 nsFocusManager::~nsFocusManager()
156 {
157 Preferences::RemoveObservers(this, kObservedPrefs);
158
159 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
160 if (obs) {
161 obs->RemoveObserver(this, "xpcom-shutdown");
162 }
163 }
164
165 // static
166 nsresult
167 nsFocusManager::Init()
168 {
169 nsFocusManager* fm = new nsFocusManager();
170 NS_ENSURE_TRUE(fm, NS_ERROR_OUT_OF_MEMORY);
171 NS_ADDREF(fm);
172 sInstance = fm;
173
174 #ifdef PR_LOGGING
175 gFocusLog = PR_NewLogModule("Focus");
176 gFocusNavigationLog = PR_NewLogModule("FocusNavigation");
177 #endif
178
179 nsIContent::sTabFocusModelAppliesToXUL =
180 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
181 nsIContent::sTabFocusModelAppliesToXUL);
182
183 sMouseFocusesFormControl =
184 Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
185
186 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
187
188 Preferences::AddWeakObservers(fm, kObservedPrefs);
189
190 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
191 if (obs) {
192 obs->AddObserver(fm, "xpcom-shutdown", true);
193 }
194
195 return NS_OK;
196 }
197
198 // static
199 void
200 nsFocusManager::Shutdown()
201 {
202 NS_IF_RELEASE(sInstance);
203 }
204
205 NS_IMETHODIMP
206 nsFocusManager::Observe(nsISupports *aSubject,
207 const char *aTopic,
208 const char16_t *aData)
209 {
210 if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
211 nsDependentString data(aData);
212 if (data.EqualsLiteral("accessibility.browsewithcaret")) {
213 UpdateCaretForCaretBrowsingMode();
214 }
215 else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
216 nsIContent::sTabFocusModelAppliesToXUL =
217 Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
218 nsIContent::sTabFocusModelAppliesToXUL);
219 }
220 else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
221 sMouseFocusesFormControl =
222 Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
223 false);
224 }
225 else if (data.EqualsLiteral("focusmanager.testmode")) {
226 sTestMode = Preferences::GetBool("focusmanager.testmode", false);
227 }
228 } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
229 mActiveWindow = nullptr;
230 mFocusedWindow = nullptr;
231 mFocusedContent = nullptr;
232 mFirstBlurEvent = nullptr;
233 mFirstFocusEvent = nullptr;
234 mWindowBeingLowered = nullptr;
235 mDelayedBlurFocusEvents.Clear();
236 mMouseDownEventHandlingDocument = nullptr;
237 }
238
239 return NS_OK;
240 }
241
242 // given a frame content node, retrieve the nsIDOMWindow displayed in it
243 static nsPIDOMWindow*
244 GetContentWindow(nsIContent* aContent)
245 {
246 nsIDocument* doc = aContent->GetCurrentDoc();
247 if (doc) {
248 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
249 if (subdoc)
250 return subdoc->GetWindow();
251 }
252
253 return nullptr;
254 }
255
256 // get the current window for the given content node
257 static nsPIDOMWindow*
258 GetCurrentWindow(nsIContent* aContent)
259 {
260 nsIDocument *doc = aContent->GetCurrentDoc();
261 return doc ? doc->GetWindow() : nullptr;
262 }
263
264 // static
265 nsIContent*
266 nsFocusManager::GetFocusedDescendant(nsPIDOMWindow* aWindow, bool aDeep,
267 nsPIDOMWindow** aFocusedWindow)
268 {
269 NS_ENSURE_TRUE(aWindow, nullptr);
270
271 *aFocusedWindow = nullptr;
272
273 nsIContent* currentContent = nullptr;
274 nsPIDOMWindow* window = aWindow->GetOuterWindow();
275 while (window) {
276 *aFocusedWindow = window;
277 currentContent = window->GetFocusedNode();
278 if (!currentContent || !aDeep)
279 break;
280
281 window = GetContentWindow(currentContent);
282 }
283
284 NS_IF_ADDREF(*aFocusedWindow);
285
286 return currentContent;
287 }
288
289 // static
290 nsIContent*
291 nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
292 {
293 #ifdef MOZ_XUL
294 if (aContent->IsXUL()) {
295 nsCOMPtr<nsIDOMNode> inputField;
296
297 nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
298 if (textbox) {
299 textbox->GetInputField(getter_AddRefs(inputField));
300 }
301 else {
302 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
303 if (menulist) {
304 menulist->GetInputField(getter_AddRefs(inputField));
305 }
306 else if (aContent->Tag() == nsGkAtoms::scale) {
307 nsCOMPtr<nsIDocument> doc = aContent->GetCurrentDoc();
308 if (!doc)
309 return nullptr;
310
311 nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent);
312 if (children) {
313 nsIContent* child = children->Item(0);
314 if (child && child->Tag() == nsGkAtoms::slider)
315 return child;
316 }
317 }
318 }
319
320 if (inputField) {
321 nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField);
322 return retval;
323 }
324 }
325 #endif
326
327 return nullptr;
328 }
329
330 // static
331 InputContextAction::Cause
332 nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags)
333 {
334 if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
335 return InputContextAction::CAUSE_MOUSE;
336 } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
337 return InputContextAction::CAUSE_KEY;
338 }
339 return InputContextAction::CAUSE_UNKNOWN;
340 }
341
342 NS_IMETHODIMP
343 nsFocusManager::GetActiveWindow(nsIDOMWindow** aWindow)
344 {
345 NS_IF_ADDREF(*aWindow = mActiveWindow);
346 return NS_OK;
347 }
348
349 NS_IMETHODIMP
350 nsFocusManager::SetActiveWindow(nsIDOMWindow* aWindow)
351 {
352 // only top-level windows can be made active
353 nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aWindow);
354 if (piWindow)
355 piWindow = piWindow->GetOuterWindow();
356
357 NS_ENSURE_TRUE(piWindow && (piWindow == piWindow->GetPrivateRoot()),
358 NS_ERROR_INVALID_ARG);
359
360 RaiseWindow(piWindow);
361 return NS_OK;
362 }
363
364 NS_IMETHODIMP
365 nsFocusManager::GetFocusedWindow(nsIDOMWindow** aFocusedWindow)
366 {
367 NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
368 return NS_OK;
369 }
370
371 NS_IMETHODIMP nsFocusManager::SetFocusedWindow(nsIDOMWindow* aWindowToFocus)
372 {
373 LOGFOCUS(("<<SetFocusedWindow begin>>"));
374
375 nsCOMPtr<nsPIDOMWindow> windowToFocus(do_QueryInterface(aWindowToFocus));
376 NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
377
378 windowToFocus = windowToFocus->GetOuterWindow();
379
380 nsCOMPtr<nsIContent> frameContent =
381 do_QueryInterface(windowToFocus->GetFrameElementInternal());
382 if (frameContent) {
383 // pass false for aFocusChanged so that the caret does not get updated
384 // and scrolling does not occur.
385 SetFocusInner(frameContent, 0, false, true);
386 }
387 else {
388 // this is a top-level window. If the window has a child frame focused,
389 // clear the focus. Otherwise, focus should already be in this frame, or
390 // already cleared. This ensures that focus will be in this frame and not
391 // in a child.
392 nsIContent* content = windowToFocus->GetFocusedNode();
393 if (content) {
394 nsCOMPtr<nsIDOMWindow> childWindow = GetContentWindow(content);
395 if (childWindow)
396 ClearFocus(windowToFocus);
397 }
398 }
399
400 nsCOMPtr<nsPIDOMWindow> rootWindow = windowToFocus->GetPrivateRoot();
401 if (rootWindow)
402 RaiseWindow(rootWindow);
403
404 LOGFOCUS(("<<SetFocusedWindow end>>"));
405
406 return NS_OK;
407 }
408
409 NS_IMETHODIMP
410 nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement)
411 {
412 if (mFocusedContent)
413 CallQueryInterface(mFocusedContent, aFocusedElement);
414 else
415 *aFocusedElement = nullptr;
416 return NS_OK;
417 }
418
419 NS_IMETHODIMP
420 nsFocusManager::GetLastFocusMethod(nsIDOMWindow* aWindow, uint32_t* aLastFocusMethod)
421 {
422 // the focus method is stored on the inner window
423 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
424 if (window)
425 window = window->GetCurrentInnerWindow();
426 if (!window)
427 window = mFocusedWindow;
428
429 *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
430
431 NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
432 "invalid focus method");
433 return NS_OK;
434 }
435
436 NS_IMETHODIMP
437 nsFocusManager::SetFocus(nsIDOMElement* aElement, uint32_t aFlags)
438 {
439 LOGFOCUS(("<<SetFocus begin>>"));
440
441 nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
442 NS_ENSURE_ARG(newFocus);
443
444 SetFocusInner(newFocus, aFlags, true, true);
445
446 LOGFOCUS(("<<SetFocus end>>"));
447
448 return NS_OK;
449 }
450
451 NS_IMETHODIMP
452 nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags,
453 bool* aIsFocusable)
454 {
455 NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
456
457 nsCOMPtr<nsIContent> aContent = do_QueryInterface(aElement);
458
459 *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr;
460
461 return NS_OK;
462 }
463
464 NS_IMETHODIMP
465 nsFocusManager::MoveFocus(nsIDOMWindow* aWindow, nsIDOMElement* aStartElement,
466 uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement)
467 {
468 *aElement = nullptr;
469
470 #ifdef PR_LOGGING
471 LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
472
473 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG) && mFocusedWindow) {
474 nsIDocument* doc = mFocusedWindow->GetExtantDoc();
475 if (doc && doc->GetDocumentURI()) {
476 nsAutoCString spec;
477 doc->GetDocumentURI()->GetSpec(spec);
478 LOGFOCUS((" Focused Window: %p %s", mFocusedWindow.get(), spec.get()));
479 }
480 }
481
482 LOGCONTENT(" Current Focus: %s", mFocusedContent.get());
483 #endif
484
485 // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
486 // the other focus methods is already set, or we're just moving to the root
487 // or caret position.
488 if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
489 (aFlags & FOCUSMETHOD_MASK) == 0) {
490 aFlags |= FLAG_BYMOVEFOCUS;
491 }
492
493 nsCOMPtr<nsPIDOMWindow> window;
494 nsCOMPtr<nsIContent> startContent;
495 if (aStartElement) {
496 startContent = do_QueryInterface(aStartElement);
497 NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
498
499 window = GetCurrentWindow(startContent);
500 }
501 else {
502 window = aWindow ? do_QueryInterface(aWindow) : mFocusedWindow;
503 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
504 window = window->GetOuterWindow();
505 }
506
507 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
508
509 bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
510 nsCOMPtr<nsIContent> newFocus;
511 nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
512 getter_AddRefs(newFocus));
513 NS_ENSURE_SUCCESS(rv, rv);
514
515 LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
516
517 if (newFocus) {
518 // for caret movement, pass false for the aFocusChanged argument,
519 // otherwise the caret will end up moving to the focus position. This
520 // would be a problem because the caret would move to the beginning of the
521 // focused link making it impossible to navigate the caret over a link.
522 SetFocusInner(newFocus, aFlags, aType != MOVEFOCUS_CARET, true);
523 CallQueryInterface(newFocus, aElement);
524 }
525 else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
526 // no content was found, so clear the focus for these two types.
527 ClearFocus(window);
528 }
529
530 LOGFOCUS(("<<MoveFocus end>>"));
531
532 return NS_OK;
533 }
534
535 NS_IMETHODIMP
536 nsFocusManager::ClearFocus(nsIDOMWindow* aWindow)
537 {
538 LOGFOCUS(("<<ClearFocus begin>>"));
539
540 // if the window to clear is the focused window or an ancestor of the
541 // focused window, then blur the existing focused content. Otherwise, the
542 // focus is somewhere else so just update the current node.
543 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
544 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
545
546 window = window->GetOuterWindow();
547 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
548
549 if (IsSameOrAncestor(window, mFocusedWindow)) {
550 bool isAncestor = (window != mFocusedWindow);
551 if (Blur(window, nullptr, isAncestor, true)) {
552 // if we are clearing the focus on an ancestor of the focused window,
553 // the ancestor will become the new focused window, so focus it
554 if (isAncestor)
555 Focus(window, nullptr, 0, true, false, false, true);
556 }
557 }
558 else {
559 window->SetFocusedNode(nullptr);
560 }
561
562 LOGFOCUS(("<<ClearFocus end>>"));
563
564 return NS_OK;
565 }
566
567 NS_IMETHODIMP
568 nsFocusManager::GetFocusedElementForWindow(nsIDOMWindow* aWindow,
569 bool aDeep,
570 nsIDOMWindow** aFocusedWindow,
571 nsIDOMElement** aElement)
572 {
573 *aElement = nullptr;
574 if (aFocusedWindow)
575 *aFocusedWindow = nullptr;
576
577 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
578 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
579
580 window = window->GetOuterWindow();
581 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
582
583 nsCOMPtr<nsPIDOMWindow> focusedWindow;
584 nsCOMPtr<nsIContent> focusedContent =
585 GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
586 if (focusedContent)
587 CallQueryInterface(focusedContent, aElement);
588
589 if (aFocusedWindow)
590 NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
591
592 return NS_OK;
593 }
594
595 NS_IMETHODIMP
596 nsFocusManager::MoveCaretToFocus(nsIDOMWindow* aWindow)
597 {
598 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
599 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
600 if (dsti) {
601 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
602 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
603 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
604
605 // don't move the caret for editable documents
606 bool isEditable;
607 docShell->GetEditable(&isEditable);
608 if (isEditable)
609 return NS_OK;
610
611 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
612 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
613
614 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aWindow));
615 nsCOMPtr<nsIContent> content = window->GetFocusedNode();
616 if (content)
617 MoveCaretToFocus(presShell, content);
618 }
619 }
620
621 return NS_OK;
622 }
623
624 NS_IMETHODIMP
625 nsFocusManager::WindowRaised(nsIDOMWindow* aWindow)
626 {
627 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
628 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
629
630 #ifdef PR_LOGGING
631 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
632 LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
633 nsAutoCString spec;
634 nsIDocument* doc = window->GetExtantDoc();
635 if (doc && doc->GetDocumentURI()) {
636 doc->GetDocumentURI()->GetSpec(spec);
637 LOGFOCUS((" Raised Window: %p %s", aWindow, spec.get()));
638 }
639 if (mActiveWindow) {
640 doc = mActiveWindow->GetExtantDoc();
641 if (doc && doc->GetDocumentURI()) {
642 doc->GetDocumentURI()->GetSpec(spec);
643 LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(), spec.get()));
644 }
645 }
646 }
647 #endif
648
649 if (mActiveWindow == window) {
650 // The window is already active, so there is no need to focus anything,
651 // but make sure that the right widget is focused. This is a special case
652 // for Windows because when restoring a minimized window, a second
653 // activation will occur and the top-level widget could be focused instead
654 // of the child we want. We solve this by calling SetFocus to ensure that
655 // what the focus manager thinks should be the current widget is actually
656 // focused.
657 EnsureCurrentWidgetFocused();
658 return NS_OK;
659 }
660
661 // lower the existing window, if any. This shouldn't happen usually.
662 if (mActiveWindow)
663 WindowLowered(mActiveWindow);
664
665 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(aWindow));
666 nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(webnav));
667 // If there's no docShellAsItem, this window must have been closed,
668 // in that case there is no tree owner.
669 NS_ENSURE_TRUE(docShellAsItem, NS_OK);
670
671 // set this as the active window
672 mActiveWindow = window;
673
674 // ensure that the window is enabled and visible
675 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
676 docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
677 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
678 if (baseWindow) {
679 bool isEnabled = true;
680 if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
681 return NS_ERROR_FAILURE;
682 }
683
684 if (!sTestMode) {
685 baseWindow->SetVisibility(true);
686 }
687 }
688
689 // inform the DOM window that it has activated, so that the active attribute
690 // is updated on the window
691 window->ActivateOrDeactivate(true);
692
693 // send activate event
694 nsContentUtils::DispatchTrustedEvent(window->GetExtantDoc(),
695 window,
696 NS_LITERAL_STRING("activate"),
697 true, true, nullptr);
698
699 // retrieve the last focused element within the window that was raised
700 nsCOMPtr<nsPIDOMWindow> currentWindow;
701 nsCOMPtr<nsIContent> currentFocus =
702 GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
703
704 NS_ASSERTION(currentWindow, "window raised with no window current");
705 if (!currentWindow)
706 return NS_OK;
707
708 nsCOMPtr<nsIDocShell> currentDocShell = currentWindow->GetDocShell();
709
710 nsCOMPtr<nsIPresShell> presShell = currentDocShell->GetPresShell();
711 if (presShell) {
712 // disable selection mousedown state on activation
713 // XXXndeakin P3 not sure if this is necessary, but it doesn't hurt
714 nsRefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
715 frameSelection->SetMouseDownState(false);
716 }
717
718 Focus(currentWindow, currentFocus, 0, true, false, true, true);
719
720 return NS_OK;
721 }
722
723 NS_IMETHODIMP
724 nsFocusManager::WindowLowered(nsIDOMWindow* aWindow)
725 {
726 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
727 NS_ENSURE_TRUE(window && window->IsOuterWindow(), NS_ERROR_INVALID_ARG);
728
729 #ifdef PR_LOGGING
730 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
731 LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
732 nsAutoCString spec;
733 nsIDocument* doc = window->GetExtantDoc();
734 if (doc && doc->GetDocumentURI()) {
735 doc->GetDocumentURI()->GetSpec(spec);
736 LOGFOCUS((" Lowered Window: %s", spec.get()));
737 }
738 if (mActiveWindow) {
739 doc = mActiveWindow->GetExtantDoc();
740 if (doc && doc->GetDocumentURI()) {
741 doc->GetDocumentURI()->GetSpec(spec);
742 LOGFOCUS((" Active Window: %s", spec.get()));
743 }
744 }
745 }
746 #endif
747
748 if (mActiveWindow != window)
749 return NS_OK;
750
751 // clear the mouse capture as the active window has changed
752 nsIPresShell::SetCapturingContent(nullptr, 0);
753
754 // inform the DOM window that it has deactivated, so that the active
755 // attribute is updated on the window
756 window->ActivateOrDeactivate(false);
757
758 // send deactivate event
759 nsContentUtils::DispatchTrustedEvent(window->GetExtantDoc(),
760 window,
761 NS_LITERAL_STRING("deactivate"),
762 true, true, nullptr);
763
764 // keep track of the window being lowered, so that attempts to raise the
765 // window can be prevented until we return. Otherwise, focus can get into
766 // an unusual state.
767 mWindowBeingLowered = mActiveWindow;
768 mActiveWindow = nullptr;
769
770 if (mFocusedWindow)
771 Blur(nullptr, nullptr, true, true);
772
773 mWindowBeingLowered = nullptr;
774
775 return NS_OK;
776 }
777
778 nsresult
779 nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
780 {
781 NS_ENSURE_ARG(aDocument);
782 NS_ENSURE_ARG(aContent);
783
784 nsPIDOMWindow *window = aDocument->GetWindow();
785 if (!window)
786 return NS_OK;
787
788 // if the content is currently focused in the window, or is an ancestor
789 // of the currently focused element, reset the focus within that window.
790 nsIContent* content = window->GetFocusedNode();
791 if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
792 bool shouldShowFocusRing = window->ShouldShowFocusRing();
793 window->SetFocusedNode(nullptr);
794
795 // if this window is currently focused, clear the global focused
796 // element as well, but don't fire any events.
797 if (window == mFocusedWindow) {
798 mFocusedContent = nullptr;
799 }
800 else {
801 // Check if the node that was focused is an iframe or similar by looking
802 // if it has a subdocument. This would indicate that this focused iframe
803 // and its descendants will be going away. We will need to move the
804 // focus somewhere else, so just clear the focus in the toplevel window
805 // so that no element is focused.
806 nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
807 if (subdoc) {
808 nsCOMPtr<nsISupports> container = subdoc->GetContainer();
809 nsCOMPtr<nsPIDOMWindow> childWindow = do_GetInterface(container);
810 if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
811 ClearFocus(mActiveWindow);
812 }
813 }
814 }
815
816 NotifyFocusStateChange(content, shouldShowFocusRing, false);
817 }
818
819 return NS_OK;
820 }
821
822 NS_IMETHODIMP
823 nsFocusManager::WindowShown(nsIDOMWindow* aWindow, bool aNeedsFocus)
824 {
825 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
826 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
827
828 window = window->GetOuterWindow();
829
830 #ifdef PR_LOGGING
831 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
832 LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
833 nsAutoCString spec;
834 nsIDocument* doc = window->GetExtantDoc();
835 if (doc && doc->GetDocumentURI()) {
836 doc->GetDocumentURI()->GetSpec(spec);
837 LOGFOCUS(("Shown Window: %s", spec.get()));
838 }
839
840 if (mFocusedWindow) {
841 doc = mFocusedWindow->GetExtantDoc();
842 if (doc && doc->GetDocumentURI()) {
843 doc->GetDocumentURI()->GetSpec(spec);
844 LOGFOCUS((" Focused Window: %s", spec.get()));
845 }
846 }
847 }
848 #endif
849
850 if (mFocusedWindow != window)
851 return NS_OK;
852
853 if (aNeedsFocus) {
854 nsCOMPtr<nsPIDOMWindow> currentWindow;
855 nsCOMPtr<nsIContent> currentFocus =
856 GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
857 if (currentWindow)
858 Focus(currentWindow, currentFocus, 0, true, false, false, true);
859 }
860 else {
861 // Sometimes, an element in a window can be focused before the window is
862 // visible, which would mean that the widget may not be properly focused.
863 // When the window becomes visible, make sure the right widget is focused.
864 EnsureCurrentWidgetFocused();
865 }
866
867 return NS_OK;
868 }
869
870 NS_IMETHODIMP
871 nsFocusManager::WindowHidden(nsIDOMWindow* aWindow)
872 {
873 // if there is no window or it is not the same or an ancestor of the
874 // currently focused window, just return, as the current focus will not
875 // be affected.
876
877 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
878 NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
879
880 window = window->GetOuterWindow();
881
882 #ifdef PR_LOGGING
883 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
884 LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
885 nsAutoCString spec;
886 nsIDocument* doc = window->GetExtantDoc();
887 if (doc && doc->GetDocumentURI()) {
888 doc->GetDocumentURI()->GetSpec(spec);
889 LOGFOCUS((" Hide Window: %s", spec.get()));
890 }
891
892 if (mFocusedWindow) {
893 doc = mFocusedWindow->GetExtantDoc();
894 if (doc && doc->GetDocumentURI()) {
895 doc->GetDocumentURI()->GetSpec(spec);
896 LOGFOCUS((" Focused Window: %s", spec.get()));
897 }
898 }
899
900 if (mActiveWindow) {
901 doc = mActiveWindow->GetExtantDoc();
902 if (doc && doc->GetDocumentURI()) {
903 doc->GetDocumentURI()->GetSpec(spec);
904 LOGFOCUS((" Active Window: %s", spec.get()));
905 }
906 }
907 }
908 #endif
909
910 if (!IsSameOrAncestor(window, mFocusedWindow))
911 return NS_OK;
912
913 // at this point, we know that the window being hidden is either the focused
914 // window, or an ancestor of the focused window. Either way, the focus is no
915 // longer valid, so it needs to be updated.
916
917 nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget();
918
919 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
920 nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
921
922 if (oldFocusedContent && oldFocusedContent->IsInDoc()) {
923 NotifyFocusStateChange(oldFocusedContent,
924 mFocusedWindow->ShouldShowFocusRing(),
925 false);
926 window->UpdateCommands(NS_LITERAL_STRING("focus"));
927
928 if (presShell) {
929 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
930 oldFocusedContent->GetCurrentDoc(),
931 oldFocusedContent, 1, false);
932 }
933 }
934
935 nsPresContext* focusedPresContext =
936 presShell ? presShell->GetPresContext() : nullptr;
937 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
938 GetFocusMoveActionCause(0));
939 if (presShell) {
940 SetCaretVisible(presShell, false, nullptr);
941 }
942
943 // if the docshell being hidden is being destroyed, then we want to move
944 // focus somewhere else. Call ClearFocus on the toplevel window, which
945 // will have the effect of clearing the focus and moving the focused window
946 // to the toplevel window. But if the window isn't being destroyed, we are
947 // likely just loading a new document in it, so we want to maintain the
948 // focused window so that the new document gets properly focused.
949 bool beingDestroyed;
950 nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
951 docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
952 if (beingDestroyed) {
953 // There is usually no need to do anything if a toplevel window is going
954 // away, as we assume that WindowLowered will be called. However, this may
955 // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
956 // a leak. So if the active window is being destroyed, call WindowLowered
957 // directly.
958 NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
959 if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
960 WindowLowered(mActiveWindow);
961 else
962 ClearFocus(mActiveWindow);
963 return NS_OK;
964 }
965
966 // if the window being hidden is an ancestor of the focused window, adjust
967 // the focused window so that it points to the one being hidden. This
968 // ensures that the focused window isn't in a chain of frames that doesn't
969 // exist any more.
970 if (window != mFocusedWindow) {
971 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(mFocusedWindow));
972 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
973 if (dsti) {
974 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
975 dsti->GetParent(getter_AddRefs(parentDsti));
976 nsCOMPtr<nsPIDOMWindow> parentWindow = do_GetInterface(parentDsti);
977 if (parentWindow)
978 parentWindow->SetFocusedNode(nullptr);
979 }
980
981 SetFocusedWindowInternal(window);
982 }
983
984 return NS_OK;
985 }
986
987 NS_IMETHODIMP
988 nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
989 {
990 NS_ENSURE_ARG(aDocument);
991
992 // fire any delayed focus and blur events in the same order that they were added
993 for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
994 if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
995 if (!aDocument->GetInnerWindow() ||
996 !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
997 // If the document was navigated away from or is defunct, don't bother
998 // firing events on it. Note the symmetry between this condition and
999 // the similar one in nsDocument.cpp:FireOrClearDelayedEvents.
1000 mDelayedBlurFocusEvents.RemoveElementAt(i);
1001 --i;
1002 } else if (!aDocument->EventHandlingSuppressed()) {
1003 uint32_t type = mDelayedBlurFocusEvents[i].mType;
1004 nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
1005 nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
1006 mDelayedBlurFocusEvents.RemoveElementAt(i);
1007 SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, false);
1008 --i;
1009 }
1010 }
1011 }
1012
1013 return NS_OK;
1014 }
1015
1016 NS_IMETHODIMP
1017 nsFocusManager::FocusPlugin(nsIContent* aContent)
1018 {
1019 NS_ENSURE_ARG(aContent);
1020 SetFocusInner(aContent, 0, true, false);
1021 return NS_OK;
1022 }
1023
1024 /* static */
1025 void
1026 nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
1027 bool aWindowShouldShowFocusRing,
1028 bool aGettingFocus)
1029 {
1030 if (!aContent->IsElement()) {
1031 return;
1032 }
1033 EventStates eventState = NS_EVENT_STATE_FOCUS;
1034 if (aWindowShouldShowFocusRing) {
1035 eventState |= NS_EVENT_STATE_FOCUSRING;
1036 }
1037 if (aGettingFocus) {
1038 aContent->AsElement()->AddStates(eventState);
1039 } else {
1040 aContent->AsElement()->RemoveStates(eventState);
1041 }
1042 }
1043
1044 // static
1045 void
1046 nsFocusManager::EnsureCurrentWidgetFocused()
1047 {
1048 if (!mFocusedWindow || sTestMode)
1049 return;
1050
1051 // get the main child widget for the focused window and ensure that the
1052 // platform knows that this widget is focused.
1053 nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
1054 if (docShell) {
1055 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1056 if (presShell) {
1057 nsViewManager* vm = presShell->GetViewManager();
1058 if (vm) {
1059 nsCOMPtr<nsIWidget> widget;
1060 vm->GetRootWidget(getter_AddRefs(widget));
1061 if (widget)
1062 widget->SetFocus(false);
1063 }
1064 }
1065 }
1066 }
1067
1068 void
1069 nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
1070 bool aFocusChanged, bool aAdjustWidget)
1071 {
1072 // if the element is not focusable, just return and leave the focus as is
1073 nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags);
1074 if (!contentToFocus)
1075 return;
1076
1077 // check if the element to focus is a frame (iframe) containing a child
1078 // document. Frames are never directly focused; instead focusing a frame
1079 // means focus what is inside the frame. To do this, the descendant content
1080 // within the frame is retrieved and that will be focused instead.
1081 nsCOMPtr<nsPIDOMWindow> newWindow;
1082 nsCOMPtr<nsPIDOMWindow> subWindow = GetContentWindow(contentToFocus);
1083 if (subWindow) {
1084 contentToFocus = GetFocusedDescendant(subWindow, true, getter_AddRefs(newWindow));
1085 // since a window is being refocused, clear aFocusChanged so that the
1086 // caret position isn't updated.
1087 aFocusChanged = false;
1088 }
1089
1090 // unless it was set above, retrieve the window for the element to focus
1091 if (!newWindow)
1092 newWindow = GetCurrentWindow(contentToFocus);
1093
1094 // if the element is already focused, just return. Note that this happens
1095 // after the frame check above so that we compare the element that will be
1096 // focused rather than the frame it is in.
1097 if (!newWindow || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent))
1098 return;
1099
1100 // don't allow focus to be placed in docshells or descendants of docshells
1101 // that are being destroyed. Also, ensure that the page hasn't been
1102 // unloaded. The prevents content from being refocused during an unload event.
1103 nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1104 nsCOMPtr<nsIDocShell> docShell = newDocShell;
1105 while (docShell) {
1106 bool inUnload;
1107 docShell->GetIsInUnload(&inUnload);
1108 if (inUnload)
1109 return;
1110
1111 bool beingDestroyed;
1112 docShell->IsBeingDestroyed(&beingDestroyed);
1113 if (beingDestroyed)
1114 return;
1115
1116 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1117 docShell->GetParent(getter_AddRefs(parentDsti));
1118 docShell = do_QueryInterface(parentDsti);
1119 }
1120
1121 // if the new element is in the same window as the currently focused element
1122 bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
1123
1124 if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
1125 nsContentUtils::IsHandlingKeyBoardEvent()) {
1126 nsCOMPtr<nsIScriptObjectPrincipal> focused =
1127 do_QueryInterface(mFocusedWindow);
1128 nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
1129 do_QueryInterface(newWindow);
1130 nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
1131 nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
1132 if (!focusedPrincipal || !newPrincipal) {
1133 return;
1134 }
1135 bool subsumes = false;
1136 focusedPrincipal->Subsumes(newPrincipal, &subsumes);
1137 if (!subsumes && !nsContentUtils::IsCallerChrome()) {
1138 NS_WARNING("Not allowed to focus the new window!");
1139 return;
1140 }
1141 }
1142
1143 // to check if the new element is in the active window, compare the
1144 // new root docshell for the new element with the active window's docshell.
1145 bool isElementInActiveWindow = false;
1146
1147 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(newWindow);
1148 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
1149 nsCOMPtr<nsPIDOMWindow> newRootWindow;
1150 if (dsti) {
1151 nsCOMPtr<nsIDocShellTreeItem> root;
1152 dsti->GetRootTreeItem(getter_AddRefs(root));
1153 newRootWindow = do_GetInterface(root);
1154
1155 isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
1156 }
1157
1158 // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
1159 // system. We don't control event dispatch to windowed plugins on non-MacOSX,
1160 // so we can't display the "Press ESC to leave fullscreen mode" warning on
1161 // key input if a windowed plugin is focused, so just exit fullscreen
1162 // to guard against phishing.
1163 #ifndef XP_MACOSX
1164 nsIDocument* fullscreenAncestor;
1165 if (contentToFocus &&
1166 (fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) &&
1167 nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
1168 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1169 NS_LITERAL_CSTRING("DOM"),
1170 contentToFocus->OwnerDoc(),
1171 nsContentUtils::eDOM_PROPERTIES,
1172 "FocusedWindowedPluginWhileFullScreen");
1173 nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true);
1174 }
1175 #endif
1176
1177 // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1178 // shifted away from the current element if the new shell to focus is
1179 // the same or an ancestor shell of the currently focused shell.
1180 bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1181 IsSameOrAncestor(newWindow, mFocusedWindow);
1182
1183 // if the element is in the active window, frame switching is allowed and
1184 // the content is in a visible window, fire blur and focus events.
1185 bool sendFocusEvent =
1186 isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1187
1188 // When the following conditions are true:
1189 // * an element has focus
1190 // * isn't called by trusted event (i.e., called by untrusted event or by js)
1191 // * the focus is moved to another document's element
1192 // we need to check the permission.
1193 if (sendFocusEvent && mFocusedContent &&
1194 mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) {
1195 // If the caller cannot access the current focused node, the caller should
1196 // not be able to steal focus from it. E.g., When the current focused node
1197 // is in chrome, any web contents should not be able to steal the focus.
1198 nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
1199 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1200 if (!sendFocusEvent && mMouseDownEventHandlingDocument) {
1201 // However, while mouse down event is handling, the handling document's
1202 // script should be able to steal focus.
1203 domNode = do_QueryInterface(mMouseDownEventHandlingDocument);
1204 sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
1205 }
1206 }
1207
1208 LOGCONTENT("Shift Focus: %s", contentToFocus.get());
1209 LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1210 aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get()));
1211 LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
1212 isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
1213
1214 if (sendFocusEvent) {
1215 // return if blurring fails or the focus changes during the blur
1216 if (mFocusedWindow) {
1217 // if the focus is being moved to another element in the same document,
1218 // or to a descendant, pass the existing window to Blur so that the
1219 // current node in the existing window is cleared. If moving to a
1220 // window elsewhere, we want to maintain the current node in the
1221 // window but still blur it.
1222 bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
1223 // find the common ancestor of the currently focused window and the new
1224 // window. The ancestor will need to have its currently focused node
1225 // cleared once the document has been blurred. Otherwise, we'll be in a
1226 // state where a document is blurred yet the chain of windows above it
1227 // still points to that document.
1228 // For instance, in the following frame tree:
1229 // A
1230 // B C
1231 // D
1232 // D is focused and we want to focus C. Once D has been blurred, we need
1233 // to clear out the focus in A, otherwise A would still maintain that B
1234 // was focused, and B that D was focused.
1235 nsCOMPtr<nsPIDOMWindow> commonAncestor;
1236 if (!isElementInFocusedWindow)
1237 commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
1238
1239 if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
1240 commonAncestor, !isElementInFocusedWindow, aAdjustWidget))
1241 return;
1242 }
1243
1244 Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
1245 aFocusChanged, false, aAdjustWidget);
1246 }
1247 else {
1248 // otherwise, for inactive windows and when the caller cannot steal the
1249 // focus, update the node in the window, and raise the window if desired.
1250 if (allowFrameSwitch)
1251 AdjustWindowFocus(newWindow, true);
1252
1253 // set the focus node and method as needed
1254 uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1255 newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1256 newWindow->SetFocusedNode(contentToFocus, focusMethod);
1257 if (aFocusChanged) {
1258 nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
1259
1260 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1261 if (presShell)
1262 ScrollIntoView(presShell, contentToFocus, aFlags);
1263 }
1264
1265 // update the commands even when inactive so that the attributes for that
1266 // window are up to date.
1267 if (allowFrameSwitch)
1268 newWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1269
1270 if (aFlags & FLAG_RAISE)
1271 RaiseWindow(newRootWindow);
1272 }
1273 }
1274
1275 bool
1276 nsFocusManager::IsSameOrAncestor(nsPIDOMWindow* aPossibleAncestor,
1277 nsPIDOMWindow* aWindow)
1278 {
1279 nsCOMPtr<nsIWebNavigation> awebnav(do_GetInterface(aPossibleAncestor));
1280 nsCOMPtr<nsIDocShellTreeItem> ancestordsti = do_QueryInterface(awebnav);
1281
1282 nsCOMPtr<nsIWebNavigation> fwebnav(do_GetInterface(aWindow));
1283 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(fwebnav);
1284 while (dsti) {
1285 if (dsti == ancestordsti)
1286 return true;
1287 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1288 dsti->GetParent(getter_AddRefs(parentDsti));
1289 dsti.swap(parentDsti);
1290 }
1291
1292 return false;
1293 }
1294
1295 already_AddRefed<nsPIDOMWindow>
1296 nsFocusManager::GetCommonAncestor(nsPIDOMWindow* aWindow1,
1297 nsPIDOMWindow* aWindow2)
1298 {
1299 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(aWindow1));
1300 nsCOMPtr<nsIDocShellTreeItem> dsti1 = do_QueryInterface(webnav);
1301 NS_ENSURE_TRUE(dsti1, nullptr);
1302
1303 webnav = do_GetInterface(aWindow2);
1304 nsCOMPtr<nsIDocShellTreeItem> dsti2 = do_QueryInterface(webnav);
1305 NS_ENSURE_TRUE(dsti2, nullptr);
1306
1307 nsAutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
1308 do {
1309 parents1.AppendElement(dsti1);
1310 nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1311 dsti1->GetParent(getter_AddRefs(parentDsti1));
1312 dsti1.swap(parentDsti1);
1313 } while (dsti1);
1314 do {
1315 parents2.AppendElement(dsti2);
1316 nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1317 dsti2->GetParent(getter_AddRefs(parentDsti2));
1318 dsti2.swap(parentDsti2);
1319 } while (dsti2);
1320
1321 uint32_t pos1 = parents1.Length();
1322 uint32_t pos2 = parents2.Length();
1323 nsIDocShellTreeItem* parent = nullptr;
1324 uint32_t len;
1325 for (len = std::min(pos1, pos2); len > 0; --len) {
1326 nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1327 nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1328 if (child1 != child2) {
1329 break;
1330 }
1331 parent = child1;
1332 }
1333
1334 nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(parent);
1335 return window.forget();
1336 }
1337
1338 void
1339 nsFocusManager::AdjustWindowFocus(nsPIDOMWindow* aWindow,
1340 bool aCheckPermission)
1341 {
1342 bool isVisible = IsWindowVisible(aWindow);
1343
1344 nsCOMPtr<nsPIDOMWindow> window(aWindow);
1345 while (window) {
1346 // get the containing <iframe> or equivalent element so that it can be
1347 // focused below.
1348 nsCOMPtr<nsIContent> frameContent =
1349 do_QueryInterface(window->GetFrameElementInternal());
1350
1351 nsCOMPtr<nsIWebNavigation> webnav(do_GetInterface(window));
1352 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
1353 if (!dsti)
1354 return;
1355 nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1356 dsti->GetParent(getter_AddRefs(parentDsti));
1357
1358 window = do_GetInterface(parentDsti);
1359 if (window) {
1360 // if the parent window is visible but aWindow was not, then we have
1361 // likely moved up and out from a hidden tab to the browser window, or a
1362 // similar such arrangement. Stop adjusting the current nodes.
1363 if (IsWindowVisible(window) != isVisible)
1364 break;
1365
1366 // When aCheckPermission is true, we should check whether the caller can
1367 // access the window or not. If it cannot access, we should stop the
1368 // adjusting.
1369 if (aCheckPermission && !nsContentUtils::CanCallerAccess(window))
1370 break;
1371
1372 window->SetFocusedNode(frameContent);
1373 }
1374 }
1375 }
1376
1377 bool
1378 nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
1379 {
1380 if (!aWindow || aWindow->IsFrozen())
1381 return false;
1382
1383 // Check if the inner window is frozen as well. This can happen when a focus change
1384 // occurs while restoring a previous page.
1385 nsPIDOMWindow* innerWindow = aWindow->GetCurrentInnerWindow();
1386 if (!innerWindow || innerWindow->IsFrozen())
1387 return false;
1388
1389 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1390 nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
1391 if (!baseWin)
1392 return false;
1393
1394 bool visible = false;
1395 baseWin->GetVisibility(&visible);
1396 return visible;
1397 }
1398
1399 bool
1400 nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
1401 {
1402 NS_PRECONDITION(aContent, "aContent must not be NULL");
1403 NS_PRECONDITION(aContent->IsInDoc(), "aContent must be in a document");
1404
1405 // If aContent is in designMode, the root element is not focusable.
1406 // NOTE: in designMode, most elements are not focusable, just the document is
1407 // focusable.
1408 // Also, if aContent is not editable but it isn't in designMode, it's not
1409 // focusable.
1410 // And in userfocusignored context nothing is focusable.
1411 nsIDocument* doc = aContent->GetCurrentDoc();
1412 NS_ASSERTION(doc, "aContent must have current document");
1413 return aContent == doc->GetRootElement() &&
1414 (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
1415 nsContentUtils::IsUserFocusIgnored(aContent));
1416 }
1417
1418 nsIContent*
1419 nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
1420 {
1421 if (!aContent)
1422 return nullptr;
1423
1424 // this is a special case for some XUL elements where an anonymous child is
1425 // actually focusable and not the element itself.
1426 nsIContent* redirectedFocus = GetRedirectedFocus(aContent);
1427 if (redirectedFocus)
1428 return CheckIfFocusable(redirectedFocus, aFlags);
1429
1430 nsCOMPtr<nsIDocument> doc = aContent->GetCurrentDoc();
1431 // can't focus elements that are not in documents
1432 if (!doc) {
1433 LOGCONTENT("Cannot focus %s because content not in document", aContent)
1434 return nullptr;
1435 }
1436
1437 // Make sure that our frames are up to date
1438 doc->FlushPendingNotifications(Flush_Layout);
1439
1440 nsIPresShell *shell = doc->GetShell();
1441 if (!shell)
1442 return nullptr;
1443
1444 // the root content can always be focused,
1445 // except in userfocusignored context.
1446 if (aContent == doc->GetRootElement())
1447 return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent;
1448
1449 // cannot focus content in print preview mode. Only the root can be focused.
1450 nsPresContext* presContext = shell->GetPresContext();
1451 if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
1452 LOGCONTENT("Cannot focus %s while in print preview", aContent)
1453 return nullptr;
1454 }
1455
1456 nsIFrame* frame = aContent->GetPrimaryFrame();
1457 if (!frame) {
1458 LOGCONTENT("Cannot focus %s as it has no frame", aContent)
1459 return nullptr;
1460 }
1461
1462 if (aContent->Tag() == nsGkAtoms::area && aContent->IsHTML()) {
1463 // HTML areas do not have their own frame, and the img frame we get from
1464 // GetPrimaryFrame() is not relevant as to whether it is focusable or
1465 // not, so we have to do all the relevant checks manually for them.
1466 return frame->IsVisibleConsideringAncestors() &&
1467 aContent->IsFocusable() ? aContent : nullptr;
1468 }
1469
1470 // if this is a child frame content node, check if it is visible and
1471 // call the content node's IsFocusable method instead of the frame's
1472 // IsFocusable method. This skips checking the style system and ensures that
1473 // offscreen browsers can still be focused.
1474 nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
1475 if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
1476 const nsStyleUserInterface* ui = frame->StyleUserInterface();
1477 int32_t tabIndex = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE ||
1478 ui->mUserFocus == NS_STYLE_USER_FOCUS_NONE) ? -1 : 0;
1479 return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
1480 }
1481
1482 return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
1483 }
1484
1485 bool
1486 nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
1487 nsPIDOMWindow* aAncestorWindowToFocus,
1488 bool aIsLeavingDocument,
1489 bool aAdjustWidgets)
1490 {
1491 LOGFOCUS(("<<Blur begin>>"));
1492
1493 // hold a reference to the focused content, which may be null
1494 nsCOMPtr<nsIContent> content = mFocusedContent;
1495 if (content) {
1496 if (!content->IsInDoc()) {
1497 mFocusedContent = nullptr;
1498 return true;
1499 }
1500 if (content == mFirstBlurEvent)
1501 return true;
1502 }
1503
1504 // hold a reference to the focused window
1505 nsCOMPtr<nsPIDOMWindow> window = mFocusedWindow;
1506 if (!window) {
1507 mFocusedContent = nullptr;
1508 return true;
1509 }
1510
1511 nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1512 if (!docShell) {
1513 mFocusedContent = nullptr;
1514 return true;
1515 }
1516
1517 // Keep a ref to presShell since dispatching the DOM event may cause
1518 // the document to be destroyed.
1519 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1520 if (!presShell) {
1521 mFocusedContent = nullptr;
1522 return true;
1523 }
1524
1525 bool clearFirstBlurEvent = false;
1526 if (!mFirstBlurEvent) {
1527 mFirstBlurEvent = content;
1528 clearFirstBlurEvent = true;
1529 }
1530
1531 nsPresContext* focusedPresContext =
1532 mActiveWindow ? presShell->GetPresContext() : nullptr;
1533 IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1534 GetFocusMoveActionCause(0));
1535
1536 // now adjust the actual focus, by clearing the fields in the focus manager
1537 // and in the window.
1538 mFocusedContent = nullptr;
1539 bool shouldShowFocusRing = window->ShouldShowFocusRing();
1540 if (aWindowToClear)
1541 aWindowToClear->SetFocusedNode(nullptr);
1542
1543 LOGCONTENT("Element %s has been blurred", content.get());
1544
1545 // Don't fire blur event on the root content which isn't editable.
1546 bool sendBlurEvent =
1547 content && content->IsInDoc() && !IsNonFocusableRoot(content);
1548 if (content) {
1549 if (sendBlurEvent) {
1550 NotifyFocusStateChange(content, shouldShowFocusRing, false);
1551 }
1552
1553 // if an object/plug-in/remote browser is being blurred, move the system focus
1554 // to the parent window, otherwise events will still get fired at the plugin.
1555 // But don't do this if we are blurring due to the window being lowered,
1556 // otherwise, the parent window can get raised again.
1557 if (mActiveWindow) {
1558 nsIFrame* contentFrame = content->GetPrimaryFrame();
1559 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1560 if (aAdjustWidgets && objectFrame && !sTestMode) {
1561 // note that the presshell's widget is being retrieved here, not the one
1562 // for the object frame.
1563 nsViewManager* vm = presShell->GetViewManager();
1564 if (vm) {
1565 nsCOMPtr<nsIWidget> widget;
1566 vm->GetRootWidget(getter_AddRefs(widget));
1567 if (widget)
1568 widget->SetFocus(false);
1569 }
1570 }
1571
1572 // if the object being blurred is a remote browser, deactivate remote content
1573 if (TabParent* remote = TabParent::GetFrom(content)) {
1574 remote->Deactivate();
1575 LOGFOCUS(("Remote browser deactivated"));
1576 }
1577 }
1578 }
1579
1580 bool result = true;
1581 if (sendBlurEvent) {
1582 // if there is an active window, update commands. If there isn't an active
1583 // window, then this was a blur caused by the active window being lowered,
1584 // so there is no need to update the commands
1585 if (mActiveWindow)
1586 window->UpdateCommands(NS_LITERAL_STRING("focus"));
1587
1588 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
1589 content->GetCurrentDoc(), content, 1, false);
1590 }
1591
1592 // if we are leaving the document or the window was lowered, make the caret
1593 // invisible.
1594 if (aIsLeavingDocument || !mActiveWindow)
1595 SetCaretVisible(presShell, false, nullptr);
1596
1597 // at this point, it is expected that this window will be still be
1598 // focused, but the focused content will be null, as it was cleared before
1599 // the event. If this isn't the case, then something else was focused during
1600 // the blur event above and we should just return. However, if
1601 // aIsLeavingDocument is set, a new document is desired, so make sure to
1602 // blur the document and window.
1603 if (mFocusedWindow != window ||
1604 (mFocusedContent != nullptr && !aIsLeavingDocument)) {
1605 result = false;
1606 }
1607 else if (aIsLeavingDocument) {
1608 window->TakeFocus(false, 0);
1609
1610 // clear the focus so that the ancestor frame hierarchy is in the correct
1611 // state. Pass true because aAncestorWindowToFocus is thought to be
1612 // focused at this point.
1613 if (aAncestorWindowToFocus)
1614 aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true);
1615
1616 SetFocusedWindowInternal(nullptr);
1617 mFocusedContent = nullptr;
1618
1619 // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
1620 // that the check is made for suppressed documents. Check to ensure that
1621 // the document isn't null in case someone closed it during the blur above
1622 nsIDocument* doc = window->GetExtantDoc();
1623 if (doc)
1624 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, doc, 1, false);
1625 if (mFocusedWindow == nullptr)
1626 SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell, doc, window, 1, false);
1627
1628 // check if a different window was focused
1629 result = (mFocusedWindow == nullptr && mActiveWindow);
1630 }
1631 else if (mActiveWindow) {
1632 // Otherwise, the blur of the element without blurring the document
1633 // occurred normally. Call UpdateCaret to redisplay the caret at the right
1634 // location within the document. This is needed to ensure that the caret
1635 // used for caret browsing is made visible again when an input field is
1636 // blurred.
1637 UpdateCaret(false, true, nullptr);
1638 }
1639
1640 if (clearFirstBlurEvent)
1641 mFirstBlurEvent = nullptr;
1642
1643 return result;
1644 }
1645
1646 void
1647 nsFocusManager::Focus(nsPIDOMWindow* aWindow,
1648 nsIContent* aContent,
1649 uint32_t aFlags,
1650 bool aIsNewDocument,
1651 bool aFocusChanged,
1652 bool aWindowRaised,
1653 bool aAdjustWidgets)
1654 {
1655 LOGFOCUS(("<<Focus begin>>"));
1656
1657 if (!aWindow)
1658 return;
1659
1660 if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent))
1661 return;
1662
1663 // Keep a reference to the presShell since dispatching the DOM event may
1664 // cause the document to be destroyed.
1665 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1666 if (!docShell)
1667 return;
1668
1669 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1670 if (!presShell)
1671 return;
1672
1673 // If the focus actually changed, set the focus method (mouse, keyboard, etc).
1674 // Otherwise, just get the current focus method and use that. This ensures
1675 // that the method is set during the document and window focus events.
1676 uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1677 aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1678
1679 if (!IsWindowVisible(aWindow)) {
1680 // if the window isn't visible, for instance because it is a hidden tab,
1681 // update the current focus and scroll it into view but don't do anything else
1682 if (CheckIfFocusable(aContent, aFlags)) {
1683 aWindow->SetFocusedNode(aContent, focusMethod);
1684 if (aFocusChanged)
1685 ScrollIntoView(presShell, aContent, aFlags);
1686 }
1687 return;
1688 }
1689
1690 bool clearFirstFocusEvent = false;
1691 if (!mFirstFocusEvent) {
1692 mFirstFocusEvent = aContent;
1693 clearFirstFocusEvent = true;
1694 }
1695
1696 #ifdef PR_LOGGING
1697 LOGCONTENT("Element %s has been focused", aContent);
1698
1699 if (PR_LOG_TEST(gFocusLog, PR_LOG_DEBUG)) {
1700 nsIDocument* docm = aWindow->GetExtantDoc();
1701 if (docm) {
1702 LOGCONTENT(" from %s", docm->GetRootElement());
1703 }
1704 LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
1705 aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
1706 }
1707 #endif
1708
1709 if (aIsNewDocument) {
1710 // if this is a new document, update the parent chain of frames so that
1711 // focus can be traversed from the top level down to the newly focused
1712 // window.
1713 AdjustWindowFocus(aWindow, false);
1714
1715 // Update the window touch registration to reflect the state of
1716 // the new document that got focus
1717 aWindow->UpdateTouchState();
1718 }
1719
1720 // indicate that the window has taken focus.
1721 if (aWindow->TakeFocus(true, focusMethod))
1722 aIsNewDocument = true;
1723
1724 SetFocusedWindowInternal(aWindow);
1725
1726 // Update the system focus by focusing the root widget. But avoid this
1727 // if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
1728 // own widget and is either already focused or is about to be focused.
1729 nsCOMPtr<nsIWidget> objectFrameWidget;
1730 if (aContent) {
1731 nsIFrame* contentFrame = aContent->GetPrimaryFrame();
1732 nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1733 if (objectFrame)
1734 objectFrameWidget = objectFrame->GetWidget();
1735 }
1736 if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
1737 nsViewManager* vm = presShell->GetViewManager();
1738 if (vm) {
1739 nsCOMPtr<nsIWidget> widget;
1740 vm->GetRootWidget(getter_AddRefs(widget));
1741 if (widget)
1742 widget->SetFocus(false);
1743 }
1744 }
1745
1746 // if switching to a new document, first fire the focus event on the
1747 // document and then the window.
1748 if (aIsNewDocument) {
1749 nsIDocument* doc = aWindow->GetExtantDoc();
1750 // The focus change should be notified to IMEStateManager from here if
1751 // the focused content is a designMode editor since any content won't
1752 // receive focus event.
1753 if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
1754 IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
1755 GetFocusMoveActionCause(aFlags));
1756 }
1757 if (doc)
1758 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1759 doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1760 if (mFocusedWindow == aWindow && mFocusedContent == nullptr)
1761 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell, doc,
1762 aWindow, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1763 }
1764
1765 // check to ensure that the element is still focusable, and that nothing
1766 // else was focused during the events above.
1767 if (CheckIfFocusable(aContent, aFlags) &&
1768 mFocusedWindow == aWindow && mFocusedContent == nullptr) {
1769 mFocusedContent = aContent;
1770
1771 nsIContent* focusedNode = aWindow->GetFocusedNode();
1772 bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent);
1773
1774 aWindow->SetFocusedNode(aContent, focusMethod);
1775
1776 bool sendFocusEvent =
1777 aContent && aContent->IsInDoc() && !IsNonFocusableRoot(aContent);
1778 nsPresContext* presContext = presShell->GetPresContext();
1779 if (sendFocusEvent) {
1780 // if the focused element changed, scroll it into view
1781 if (aFocusChanged)
1782 ScrollIntoView(presShell, aContent, aFlags);
1783
1784 NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
1785
1786 // if this is an object/plug-in/remote browser, focus its widget. Note that we might
1787 // no longer be in the same document, due to the events we fired above when
1788 // aIsNewDocument.
1789 if (presShell->GetDocument() == aContent->GetDocument()) {
1790 if (aAdjustWidgets && objectFrameWidget && !sTestMode)
1791 objectFrameWidget->SetFocus(false);
1792
1793 // if the object being focused is a remote browser, activate remote content
1794 if (TabParent* remote = TabParent::GetFrom(aContent)) {
1795 remote->Activate();
1796 LOGFOCUS(("Remote browser activated"));
1797 }
1798 }
1799
1800 IMEStateManager::OnChangeFocus(presContext, aContent,
1801 GetFocusMoveActionCause(aFlags));
1802
1803 // as long as this focus wasn't because a window was raised, update the
1804 // commands
1805 // XXXndeakin P2 someone could adjust the focus during the update
1806 if (!aWindowRaised)
1807 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1808
1809 SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell,
1810 aContent->GetCurrentDoc(),
1811 aContent, aFlags & FOCUSMETHOD_MASK,
1812 aWindowRaised, isRefocus);
1813 } else {
1814 IMEStateManager::OnChangeFocus(presContext, nullptr,
1815 GetFocusMoveActionCause(aFlags));
1816 if (!aWindowRaised) {
1817 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1818 }
1819 }
1820 }
1821 else {
1822 // If the window focus event (fired above when aIsNewDocument) caused
1823 // the plugin not to be focusable, update the system focus by focusing
1824 // the root widget.
1825 if (aAdjustWidgets && objectFrameWidget &&
1826 mFocusedWindow == aWindow && mFocusedContent == nullptr &&
1827 !sTestMode) {
1828 nsViewManager* vm = presShell->GetViewManager();
1829 if (vm) {
1830 nsCOMPtr<nsIWidget> widget;
1831 vm->GetRootWidget(getter_AddRefs(widget));
1832 if (widget)
1833 widget->SetFocus(false);
1834 }
1835 }
1836
1837 nsPresContext* presContext = presShell->GetPresContext();
1838 IMEStateManager::OnChangeFocus(presContext, nullptr,
1839 GetFocusMoveActionCause(aFlags));
1840
1841 if (!aWindowRaised)
1842 aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
1843 }
1844
1845 // update the caret visibility and position to match the newly focused
1846 // element. However, don't update the position if this was a focus due to a
1847 // mouse click as the selection code would already have moved the caret as
1848 // needed. If this is a different document than was focused before, also
1849 // update the caret's visibility. If this is the same document, the caret
1850 // visibility should be the same as before so there is no need to update it.
1851 if (mFocusedContent == aContent)
1852 UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
1853 mFocusedContent);
1854
1855 if (clearFirstFocusEvent)
1856 mFirstFocusEvent = nullptr;
1857 }
1858
1859 class FocusBlurEvent : public nsRunnable
1860 {
1861 public:
1862 FocusBlurEvent(nsISupports* aTarget, uint32_t aType,
1863 nsPresContext* aContext, bool aWindowRaised,
1864 bool aIsRefocus)
1865 : mTarget(aTarget), mType(aType), mContext(aContext),
1866 mWindowRaised(aWindowRaised), mIsRefocus(aIsRefocus) {}
1867
1868 NS_IMETHOD Run()
1869 {
1870 InternalFocusEvent event(true, mType);
1871 event.mFlags.mBubbles = false;
1872 event.fromRaise = mWindowRaised;
1873 event.isRefocus = mIsRefocus;
1874 return EventDispatcher::Dispatch(mTarget, mContext, &event);
1875 }
1876
1877 nsCOMPtr<nsISupports> mTarget;
1878 uint32_t mType;
1879 nsRefPtr<nsPresContext> mContext;
1880 bool mWindowRaised;
1881 bool mIsRefocus;
1882 };
1883
1884 void
1885 nsFocusManager::SendFocusOrBlurEvent(uint32_t aType,
1886 nsIPresShell* aPresShell,
1887 nsIDocument* aDocument,
1888 nsISupports* aTarget,
1889 uint32_t aFocusMethod,
1890 bool aWindowRaised,
1891 bool aIsRefocus)
1892 {
1893 NS_ASSERTION(aType == NS_FOCUS_CONTENT || aType == NS_BLUR_CONTENT,
1894 "Wrong event type for SendFocusOrBlurEvent");
1895
1896 nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
1897
1898 nsCOMPtr<nsINode> n = do_QueryInterface(aTarget);
1899 if (!n) {
1900 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aTarget);
1901 n = win ? win->GetExtantDoc() : nullptr;
1902 }
1903 bool dontDispatchEvent = n && nsContentUtils::IsUserFocusIgnored(n);
1904
1905 // for focus events, if this event was from a mouse or key and event
1906 // handling on the document is suppressed, queue the event and fire it
1907 // later. For blur events, a non-zero value would be set for aFocusMethod.
1908 if (aFocusMethod && !dontDispatchEvent &&
1909 aDocument && aDocument->EventHandlingSuppressed()) {
1910 // aFlags is always 0 when aWindowRaised is true so this won't be called
1911 // on a window raise.
1912 NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
1913
1914 for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
1915 // if this event was already queued, remove it and append it to the end
1916 if (mDelayedBlurFocusEvents[i - 1].mType == aType &&
1917 mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
1918 mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
1919 mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget) {
1920 mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
1921 }
1922 }
1923
1924 mDelayedBlurFocusEvents.AppendElement(
1925 nsDelayedBlurOrFocusEvent(aType, aPresShell, aDocument, eventTarget));
1926 return;
1927 }
1928
1929 #ifdef ACCESSIBILITY
1930 nsAccessibilityService* accService = GetAccService();
1931 if (accService) {
1932 if (aType == NS_FOCUS_CONTENT)
1933 accService->NotifyOfDOMFocus(aTarget);
1934 else
1935 accService->NotifyOfDOMBlur(aTarget);
1936 }
1937 #endif
1938
1939 if (!dontDispatchEvent) {
1940 nsContentUtils::AddScriptRunner(
1941 new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(),
1942 aWindowRaised, aIsRefocus));
1943 }
1944 }
1945
1946 void
1947 nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
1948 nsIContent* aContent,
1949 uint32_t aFlags)
1950 {
1951 // if the noscroll flag isn't set, scroll the newly focused element into view
1952 if (!(aFlags & FLAG_NOSCROLL))
1953 aPresShell->ScrollContentIntoView(aContent,
1954 nsIPresShell::ScrollAxis(
1955 nsIPresShell::SCROLL_MINIMUM,
1956 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
1957 nsIPresShell::ScrollAxis(
1958 nsIPresShell::SCROLL_MINIMUM,
1959 nsIPresShell::SCROLL_IF_NOT_VISIBLE),
1960 nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
1961 }
1962
1963
1964 void
1965 nsFocusManager::RaiseWindow(nsPIDOMWindow* aWindow)
1966 {
1967 // don't raise windows that are already raised or are in the process of
1968 // being lowered
1969 if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
1970 return;
1971
1972 if (sTestMode) {
1973 // In test mode, emulate the existing window being lowered and the new
1974 // window being raised.
1975 if (mActiveWindow)
1976 WindowLowered(mActiveWindow);
1977 WindowRaised(aWindow);
1978 return;
1979 }
1980
1981 #if defined(XP_WIN)
1982 // Windows would rather we focus the child widget, otherwise, the toplevel
1983 // widget will always end up being focused. Fortunately, focusing the child
1984 // widget will also have the effect of raising the window this widget is in.
1985 // But on other platforms, we can just focus the toplevel widget to raise
1986 // the window.
1987 nsCOMPtr<nsPIDOMWindow> childWindow;
1988 GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow));
1989 if (!childWindow)
1990 childWindow = aWindow;
1991
1992 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1993 if (!docShell)
1994 return;
1995
1996 nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1997 if (!presShell)
1998 return;
1999
2000 nsViewManager* vm = presShell->GetViewManager();
2001 if (vm) {
2002 nsCOMPtr<nsIWidget> widget;
2003 vm->GetRootWidget(getter_AddRefs(widget));
2004 if (widget)
2005 widget->SetFocus(true);
2006 }
2007 #else
2008 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
2009 nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = do_QueryInterface(webnav);
2010 if (treeOwnerAsWin) {
2011 nsCOMPtr<nsIWidget> widget;
2012 treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
2013 if (widget)
2014 widget->SetFocus(true);
2015 }
2016 #endif
2017 }
2018
2019 void
2020 nsFocusManager::UpdateCaretForCaretBrowsingMode()
2021 {
2022 UpdateCaret(false, true, mFocusedContent);
2023 }
2024
2025 void
2026 nsFocusManager::UpdateCaret(bool aMoveCaretToFocus,
2027 bool aUpdateVisibility,
2028 nsIContent* aContent)
2029 {
2030 LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
2031
2032 if (!mFocusedWindow)
2033 return;
2034
2035 // this is called when a document is focused or when the caretbrowsing
2036 // preference is changed
2037 nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
2038 nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
2039 if (!dsti)
2040 return;
2041
2042 if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
2043 return; // Never browse with caret in chrome
2044 }
2045
2046 bool browseWithCaret =
2047 Preferences::GetBool("accessibility.browsewithcaret");
2048
2049 nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
2050 if (!presShell)
2051 return;
2052
2053 // If this is an editable document which isn't contentEditable, or a
2054 // contentEditable document and the node to focus is contentEditable,
2055 // return, so that we don't mess with caret visibility.
2056 bool isEditable = false;
2057 focusedDocShell->GetEditable(&isEditable);
2058
2059 if (isEditable) {
2060 nsCOMPtr<nsIHTMLDocument> doc =
2061 do_QueryInterface(presShell->GetDocument());
2062
2063 bool isContentEditableDoc =
2064 doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
2065
2066 bool isFocusEditable =
2067 aContent && aContent->HasFlag(NODE_IS_EDITABLE);
2068 if (!isContentEditableDoc || isFocusEditable)
2069 return;
2070 }
2071
2072 if (!isEditable && aMoveCaretToFocus)
2073 MoveCaretToFocus(presShell, aContent);
2074
2075 if (!aUpdateVisibility)
2076 return;
2077
2078 // XXXndeakin this doesn't seem right. It should be checking for this only
2079 // on the nearest ancestor frame which is a chrome frame. But this is
2080 // what the existing code does, so just leave it for now.
2081 if (!browseWithCaret) {
2082 nsCOMPtr<nsIContent> docContent =
2083 do_QueryInterface(mFocusedWindow->GetFrameElementInternal());
2084 if (docContent)
2085 browseWithCaret = docContent->AttrValueIs(kNameSpaceID_None,
2086 nsGkAtoms::showcaret,
2087 NS_LITERAL_STRING("true"),
2088 eCaseMatters);
2089 }
2090
2091 SetCaretVisible(presShell, browseWithCaret, aContent);
2092 }
2093
2094 void
2095 nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
2096 {
2097 // domDoc is a document interface we can create a range with
2098 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aPresShell->GetDocument());
2099 if (domDoc) {
2100 nsRefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2101 nsCOMPtr<nsISelection> domSelection = frameSelection->
2102 GetSelection(nsISelectionController::SELECTION_NORMAL);
2103 if (domSelection) {
2104 nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
2105 // First clear the selection. This way, if there is no currently focused
2106 // content, the selection will just be cleared.
2107 domSelection->RemoveAllRanges();
2108 if (currentFocusNode) {
2109 nsCOMPtr<nsIDOMRange> newRange;
2110 nsresult rv = domDoc->CreateRange(getter_AddRefs(newRange));
2111 if (NS_SUCCEEDED(rv)) {
2112 // Set the range to the start of the currently focused node
2113 // Make sure it's collapsed
2114 newRange->SelectNodeContents(currentFocusNode);
2115 nsCOMPtr<nsIDOMNode> firstChild;
2116 currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
2117 if (!firstChild ||
2118 aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
2119 // If current focus node is a leaf, set range to before the
2120 // node by using the parent as a container.
2121 // This prevents it from appearing as selected.
2122 newRange->SetStartBefore(currentFocusNode);
2123 newRange->SetEndBefore(currentFocusNode);
2124 }
2125 domSelection->AddRange(newRange);
2126 domSelection->CollapseToStart();
2127 }
2128 }
2129 }
2130 }
2131 }
2132
2133 nsresult
2134 nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
2135 bool aVisible,
2136 nsIContent* aContent)
2137 {
2138 // When browsing with caret, make sure caret is visible after new focus
2139 // Return early if there is no caret. This can happen for the testcase
2140 // for bug 308025 where a window is closed in a blur handler.
2141 nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
2142 if (!caret)
2143 return NS_OK;
2144
2145 bool caretVisible = false;
2146 caret->GetCaretVisible(&caretVisible);
2147 if (!aVisible && !caretVisible)
2148 return NS_OK;
2149
2150 nsRefPtr<nsFrameSelection> frameSelection;
2151 if (aContent) {
2152 NS_ASSERTION(aContent->GetDocument() == aPresShell->GetDocument(),
2153 "Wrong document?");
2154 nsIFrame *focusFrame = aContent->GetPrimaryFrame();
2155 if (focusFrame)
2156 frameSelection = focusFrame->GetFrameSelection();
2157 }
2158
2159 nsRefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
2160
2161 if (docFrameSelection && caret &&
2162 (frameSelection == docFrameSelection || !aContent)) {
2163 nsISelection* domSelection = docFrameSelection->
2164 GetSelection(nsISelectionController::SELECTION_NORMAL);
2165 if (domSelection) {
2166 // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
2167 caret->SetCaretVisible(false);
2168
2169 // Caret must blink on non-editable elements
2170 caret->SetIgnoreUserModify(true);
2171 // Tell the caret which selection to use
2172 caret->SetCaretDOMSelection(domSelection);
2173
2174 // In content, we need to set the caret. The only special case is edit
2175 // fields, which have a different frame selection from the document.
2176 // They will take care of making the caret visible themselves.
2177
2178 nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
2179 if (!selCon)
2180 return NS_ERROR_FAILURE;
2181
2182 selCon->SetCaretReadOnly(false);
2183 selCon->SetCaretEnabled(aVisible);
2184 caret->SetCaretVisible(aVisible);
2185 }
2186 }
2187
2188 return NS_OK;
2189 }
2190
2191 nsresult
2192 nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
2193 nsIPresShell* aPresShell,
2194 nsIContent **aStartContent,
2195 nsIContent **aEndContent)
2196 {
2197 *aStartContent = *aEndContent = nullptr;
2198 nsresult rv = NS_ERROR_FAILURE;
2199
2200 nsPresContext* presContext = aPresShell->GetPresContext();
2201 NS_ASSERTION(presContext, "mPresContent is null!!");
2202
2203 nsRefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2204
2205 nsCOMPtr<nsISelection> domSelection;
2206 if (frameSelection) {
2207 domSelection = frameSelection->
2208 GetSelection(nsISelectionController::SELECTION_NORMAL);
2209 }
2210
2211 nsCOMPtr<nsIDOMNode> startNode, endNode;
2212 bool isCollapsed = false;
2213 nsCOMPtr<nsIContent> startContent, endContent;
2214 int32_t startOffset = 0;
2215 if (domSelection) {
2216 domSelection->GetIsCollapsed(&isCollapsed);
2217 nsCOMPtr<nsIDOMRange> domRange;
2218 rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
2219 if (domRange) {
2220 domRange->GetStartContainer(getter_AddRefs(startNode));
2221 domRange->GetEndContainer(getter_AddRefs(endNode));
2222 domRange->GetStartOffset(&startOffset);
2223
2224 nsIContent *childContent = nullptr;
2225
2226 startContent = do_QueryInterface(startNode);
2227 if (startContent && startContent->IsElement()) {
2228 NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
2229 childContent = startContent->GetChildAt(startOffset);
2230 if (childContent) {
2231 startContent = childContent;
2232 }
2233 }
2234
2235 endContent = do_QueryInterface(endNode);
2236 if (endContent && endContent->IsElement()) {
2237 int32_t endOffset = 0;
2238 domRange->GetEndOffset(&endOffset);
2239 NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
2240 childContent = endContent->GetChildAt(endOffset);
2241 if (childContent) {
2242 endContent = childContent;
2243 }
2244 }
2245 }
2246 }
2247 else {
2248 rv = NS_ERROR_INVALID_ARG;
2249 }
2250
2251 nsIFrame *startFrame = nullptr;
2252 if (startContent) {
2253 startFrame = startContent->GetPrimaryFrame();
2254 if (isCollapsed) {
2255 // Next check to see if our caret is at the very end of a node
2256 // If so, the caret is actually sitting in front of the next
2257 // logical frame's primary node - so for this case we need to
2258 // change caretContent to that node.
2259
2260 if (startContent->NodeType() == nsIDOMNode::TEXT_NODE) {
2261 nsAutoString nodeValue;
2262 startContent->AppendTextTo(nodeValue);
2263
2264 bool isFormControl =
2265 startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
2266
2267 if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl &&
2268 startContent != aDocument->GetRootElement()) {
2269 // Yes, indeed we were at the end of the last node
2270 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2271 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2272 presContext, startFrame,
2273 eLeaf,
2274 false, // aVisual
2275 false, // aLockInScrollView
2276 true // aFollowOOFs
2277 );
2278 NS_ENSURE_SUCCESS(rv, rv);
2279
2280 nsIFrame *newCaretFrame = nullptr;
2281 nsCOMPtr<nsIContent> newCaretContent = startContent;
2282 bool endOfSelectionInStartNode(startContent == endContent);
2283 do {
2284 // Continue getting the next frame until the primary content for the frame
2285 // we are on changes - we don't want to be stuck in the same place
2286 frameTraversal->Next();
2287 newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2288 if (nullptr == newCaretFrame)
2289 break;
2290 newCaretContent = newCaretFrame->GetContent();
2291 } while (!newCaretContent || newCaretContent == startContent);
2292
2293 if (newCaretFrame && newCaretContent) {
2294 // If the caret is exactly at the same position of the new frame,
2295 // then we can use the newCaretFrame and newCaretContent for our position
2296 nsRefPtr<nsCaret> caret = aPresShell->GetCaret();
2297 nsRect caretRect;
2298 nsIFrame *frame = caret->GetGeometry(domSelection, &caretRect);
2299 if (frame) {
2300 nsPoint caretWidgetOffset;
2301 nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
2302 caretRect.MoveBy(caretWidgetOffset);
2303 nsPoint newCaretOffset;
2304 nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
2305 if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
2306 caretRect.x == newCaretOffset.x) {
2307 // The caret is at the start of the new element.
2308 startFrame = newCaretFrame;
2309 startContent = newCaretContent;
2310 if (endOfSelectionInStartNode) {
2311 endContent = newCaretContent; // Ensure end of selection is not before start
2312 }
2313 }
2314 }
2315 }
2316 }
2317 }
2318 }
2319 }
2320
2321 *aStartContent = startContent;
2322 *aEndContent = endContent;
2323 NS_IF_ADDREF(*aStartContent);
2324 NS_IF_ADDREF(*aEndContent);
2325
2326 return rv;
2327 }
2328
2329 nsresult
2330 nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
2331 nsIContent* aStartContent,
2332 int32_t aType, bool aNoParentTraversal,
2333 nsIContent** aNextContent)
2334 {
2335 *aNextContent = nullptr;
2336
2337 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2338 if (!docShell)
2339 return NS_OK;
2340
2341 nsCOMPtr<nsIContent> startContent = aStartContent;
2342 if (!startContent && aType != MOVEFOCUS_CARET) {
2343 if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
2344 // When moving between documents, make sure to get the right
2345 // starting content in a descendant.
2346 nsCOMPtr<nsPIDOMWindow> focusedWindow;
2347 startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
2348 }
2349 else {
2350 startContent = aWindow->GetFocusedNode();
2351 }
2352 }
2353
2354 nsCOMPtr<nsIDocument> doc;
2355 if (startContent)
2356 doc = startContent->GetCurrentDoc();
2357 else
2358 doc = aWindow->GetExtantDoc();
2359 if (!doc)
2360 return NS_OK;
2361
2362 LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
2363 &nsIContent::sTabFocusModel);
2364
2365 if (aType == MOVEFOCUS_ROOT) {
2366 NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
2367 return NS_OK;
2368 }
2369 if (aType == MOVEFOCUS_FORWARDDOC) {
2370 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, true));
2371 return NS_OK;
2372 }
2373 if (aType == MOVEFOCUS_BACKWARDDOC) {
2374 NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, false));
2375 return NS_OK;
2376 }
2377
2378 nsIContent* rootContent = doc->GetRootElement();
2379 NS_ENSURE_TRUE(rootContent, NS_OK);
2380
2381 nsIPresShell *presShell = doc->GetShell();
2382 NS_ENSURE_TRUE(presShell, NS_OK);
2383
2384 if (aType == MOVEFOCUS_FIRST) {
2385 if (!aStartContent)
2386 startContent = rootContent;
2387 return GetNextTabbableContent(presShell, startContent,
2388 nullptr, startContent,
2389 true, 1, false, aNextContent);
2390 }
2391 if (aType == MOVEFOCUS_LAST) {
2392 if (!aStartContent)
2393 startContent = rootContent;
2394 return GetNextTabbableContent(presShell, startContent,
2395 nullptr, startContent,
2396 false, 0, false, aNextContent);
2397 }
2398
2399 bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
2400 bool doNavigation = true;
2401 bool ignoreTabIndex = false;
2402 // when a popup is open, we want to ensure that tab navigation occurs only
2403 // within the most recently opened panel. If a popup is open, its frame will
2404 // be stored in popupFrame.
2405 nsIFrame* popupFrame = nullptr;
2406
2407 int32_t tabIndex = forward ? 1 : 0;
2408 if (startContent) {
2409 nsIFrame* frame = startContent->GetPrimaryFrame();
2410 if (startContent->Tag() == nsGkAtoms::area &&
2411 startContent->IsHTML())
2412 startContent->IsFocusable(&tabIndex);
2413 else if (frame)
2414 frame->IsFocusable(&tabIndex, 0);
2415 else
2416 startContent->IsFocusable(&tabIndex);
2417
2418 // if the current element isn't tabbable, ignore the tabindex and just
2419 // look for the next element. The root content won't have a tabindex
2420 // so just treat this as the beginning of the tab order.
2421 if (tabIndex < 0) {
2422 tabIndex = 1;
2423 if (startContent != rootContent)
2424 ignoreTabIndex = true;
2425 }
2426
2427 // check if the focus is currently inside a popup. Elements such as the
2428 // autocomplete widget use the noautofocus attribute to allow the focus to
2429 // remain outside the popup when it is opened.
2430 if (frame) {
2431 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2432 nsGkAtoms::menuPopupFrame);
2433 }
2434
2435 if (popupFrame) {
2436 // Don't navigate outside of a popup, so pretend that the
2437 // root content is the popup itself
2438 rootContent = popupFrame->GetContent();
2439 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2440 }
2441 else if (!forward) {
2442 // If focus moves backward and when current focused node is root
2443 // content or <body> element which is editable by contenteditable
2444 // attribute, focus should move to its parent document.
2445 if (startContent == rootContent) {
2446 doNavigation = false;
2447 } else {
2448 nsIDocument* doc = startContent->GetCurrentDoc();
2449 if (startContent ==
2450 nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
2451 doNavigation = false;
2452 }
2453 }
2454 }
2455 }
2456 else {
2457 #ifdef MOZ_XUL
2458 if (aType != MOVEFOCUS_CARET) {
2459 // if there is no focus, yet a panel is open, focus the first item in
2460 // the panel
2461 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2462 if (pm)
2463 popupFrame = pm->GetTopPopup(ePopupTypePanel);
2464 }
2465 #endif
2466 if (popupFrame) {
2467 rootContent = popupFrame->GetContent();
2468 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2469 startContent = rootContent;
2470 }
2471 else {
2472 // Otherwise, for content shells, start from the location of the caret.
2473 if (docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
2474 nsCOMPtr<nsIContent> endSelectionContent;
2475 GetSelectionLocation(doc, presShell,
2476 getter_AddRefs(startContent),
2477 getter_AddRefs(endSelectionContent));
2478 // If the selection is on the rootContent, then there is no selection
2479 if (startContent == rootContent) {
2480 startContent = nullptr;
2481 }
2482
2483 if (aType == MOVEFOCUS_CARET) {
2484 // GetFocusInSelection finds a focusable link near the caret.
2485 // If there is no start content though, don't do this to avoid
2486 // focusing something unexpected.
2487 if (startContent) {
2488 GetFocusInSelection(aWindow, startContent,
2489 endSelectionContent, aNextContent);
2490 }
2491 return NS_OK;
2492 }
2493
2494 if (startContent) {
2495 // when starting from a selection, we always want to find the next or
2496 // previous element in the document. So the tabindex on elements
2497 // should be ignored.
2498 ignoreTabIndex = true;
2499 }
2500 }
2501
2502 if (!startContent) {
2503 // otherwise, just use the root content as the starting point
2504 startContent = rootContent;
2505 NS_ENSURE_TRUE(startContent, NS_OK);
2506 }
2507 }
2508 }
2509
2510 NS_ASSERTION(startContent, "starting content not set");
2511
2512 // keep a reference to the starting content. If we find that again, it means
2513 // we've iterated around completely and we don't want to adjust the focus.
2514 // The skipOriginalContentCheck will be set to true only for the first time
2515 // GetNextTabbableContent is called. This ensures that we don't break out
2516 // when nothing is focused to start with. Specifically,
2517 // GetNextTabbableContent first checks the root content -- which happens to
2518 // be the same as the start content -- when nothing is focused and tabbing
2519 // forward. Without skipOriginalContentCheck set to true, we'd end up
2520 // returning right away and focusing nothing. Luckily, GetNextTabbableContent
2521 // will never wrap around on its own, and can only return the original
2522 // content when it is called a second time or later.
2523 bool skipOriginalContentCheck = true;
2524 nsIContent* originalStartContent = startContent;
2525
2526 LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
2527 LOGFOCUSNAVIGATION((" Tabindex: %d Ignore: %d", tabIndex, ignoreTabIndex));
2528
2529 while (doc) {
2530 if (doNavigation) {
2531 nsCOMPtr<nsIContent> nextFocus;
2532 nsresult rv = GetNextTabbableContent(presShell, rootContent,
2533 skipOriginalContentCheck ? nullptr : originalStartContent,
2534 startContent, forward,
2535 tabIndex, ignoreTabIndex,
2536 getter_AddRefs(nextFocus));
2537 NS_ENSURE_SUCCESS(rv, rv);
2538
2539 // found a content node to focus.
2540 if (nextFocus) {
2541 LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
2542
2543 // as long as the found node was not the same as the starting node,
2544 // set it as the return value.
2545 if (nextFocus != originalStartContent)
2546 NS_ADDREF(*aNextContent = nextFocus);
2547 return NS_OK;
2548 }
2549
2550 if (popupFrame) {
2551 // in a popup, so start again from the beginning of the popup. However,
2552 // if we already started at the beginning, then there isn't anything to
2553 // focus, so just return
2554 if (startContent != rootContent) {
2555 startContent = rootContent;
2556 tabIndex = forward ? 1 : 0;
2557 continue;
2558 }
2559 return NS_OK;
2560 }
2561 }
2562
2563 doNavigation = true;
2564 skipOriginalContentCheck = false;
2565 ignoreTabIndex = false;
2566
2567 if (aNoParentTraversal) {
2568 if (startContent == rootContent)
2569 return NS_OK;
2570
2571 startContent = rootContent;
2572 tabIndex = forward ? 1 : 0;
2573 continue;
2574 }
2575
2576 // reached the beginning or end of the document. Traverse up to the parent
2577 // document and try again.
2578 nsCOMPtr<nsIDocShellTreeItem> docShellParent;
2579 docShell->GetParent(getter_AddRefs(docShellParent));
2580 if (docShellParent) {
2581 // move up to the parent shell and try again from there.
2582
2583 // first, get the frame element this window is inside.
2584 nsCOMPtr<nsPIDOMWindow> piWindow = do_GetInterface(docShell);
2585 NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
2586
2587 // Next, retrieve the parent docshell, document and presshell.
2588 docShell = do_QueryInterface(docShellParent);
2589 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
2590
2591 nsCOMPtr<nsPIDOMWindow> piParentWindow = do_GetInterface(docShellParent);
2592 NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
2593 doc = piParentWindow->GetExtantDoc();
2594 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2595
2596 presShell = doc->GetShell();
2597
2598 rootContent = doc->GetRootElement();
2599 startContent = do_QueryInterface(piWindow->GetFrameElementInternal());
2600 if (startContent) {
2601 nsIFrame* frame = startContent->GetPrimaryFrame();
2602 if (!frame)
2603 return NS_OK;
2604
2605 frame->IsFocusable(&tabIndex, 0);
2606 if (tabIndex < 0) {
2607 tabIndex = 1;
2608 ignoreTabIndex = true;
2609 }
2610
2611 // if the frame is inside a popup, make sure to scan only within the
2612 // popup. This handles the situation of tabbing amongst elements
2613 // inside an iframe which is itself inside a popup. Otherwise,
2614 // navigation would move outside the popup when tabbing outside the
2615 // iframe.
2616 popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
2617 nsGkAtoms::menuPopupFrame);
2618 if (popupFrame) {
2619 rootContent = popupFrame->GetContent();
2620 NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2621 }
2622 }
2623 else {
2624 startContent = rootContent;
2625 tabIndex = forward ? 1 : 0;
2626 }
2627 }
2628 else {
2629 // no parent, so call the tree owner. This will tell the embedder that
2630 // it should take the focus.
2631 bool tookFocus;
2632 docShell->TabToTreeOwner(forward, &tookFocus);
2633 // if the tree owner, took the focus, blur the current content
2634 if (tookFocus) {
2635 nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(docShell);
2636 if (window->GetFocusedNode() == mFocusedContent)
2637 Blur(mFocusedWindow, nullptr, true, true);
2638 else
2639 window->SetFocusedNode(nullptr);
2640 return NS_OK;
2641 }
2642
2643 // reset the tab index and start again from the beginning or end
2644 startContent = rootContent;
2645 tabIndex = forward ? 1 : 0;
2646 }
2647
2648 // wrapped all the way around and didn't find anything to move the focus
2649 // to, so just break out
2650 if (startContent == originalStartContent)
2651 break;
2652 }
2653
2654 return NS_OK;
2655 }
2656
2657 nsresult
2658 nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
2659 nsIContent* aRootContent,
2660 nsIContent* aOriginalStartContent,
2661 nsIContent* aStartContent,
2662 bool aForward,
2663 int32_t aCurrentTabIndex,
2664 bool aIgnoreTabIndex,
2665 nsIContent** aResultContent)
2666 {
2667 *aResultContent = nullptr;
2668
2669 nsCOMPtr<nsIContent> startContent = aStartContent;
2670 if (!startContent)
2671 return NS_OK;
2672
2673 LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
2674 LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
2675
2676 nsPresContext* presContext = aPresShell->GetPresContext();
2677
2678 bool getNextFrame = true;
2679 nsCOMPtr<nsIContent> iterStartContent = aStartContent;
2680 while (1) {
2681 nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
2682 // if there is no frame, look for another content node that has a frame
2683 if (!startFrame) {
2684 // if the root content doesn't have a frame, just return
2685 if (iterStartContent == aRootContent)
2686 return NS_OK;
2687
2688 // look for the next or previous content node in tree order
2689 iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent();
2690 // we've already skipped over the initial focused content, so we
2691 // don't want to traverse frames.
2692 getNextFrame = false;
2693 if (iterStartContent)
2694 continue;
2695
2696 // otherwise, as a last attempt, just look at the root content
2697 iterStartContent = aRootContent;
2698 continue;
2699 }
2700
2701 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2702 nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2703 presContext, startFrame,
2704 ePreOrder,
2705 false, // aVisual
2706 false, // aLockInScrollView
2707 true // aFollowOOFs
2708 );
2709 NS_ENSURE_SUCCESS(rv, rv);
2710
2711 if (iterStartContent == aRootContent) {
2712 if (!aForward) {
2713 frameTraversal->Last();
2714 } else if (aRootContent->IsFocusable()) {
2715 frameTraversal->Next();
2716 }
2717 }
2718 else if (getNextFrame &&
2719 (!iterStartContent || iterStartContent->Tag() != nsGkAtoms::area ||
2720 !iterStartContent->IsHTML())) {
2721 // Need to do special check in case we're in an imagemap which has multiple
2722 // content nodes per frame, so don't skip over the starting frame.
2723 if (aForward)
2724 frameTraversal->Next();
2725 else
2726 frameTraversal->Prev();
2727 }
2728
2729 // Walk frames to find something tabbable matching mCurrentTabIndex
2730 nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2731 while (frame) {
2732 // TabIndex not set defaults to 0 for form elements, anchors and other
2733 // elements that are normally focusable. Tabindex defaults to -1
2734 // for elements that are not normally focusable.
2735 // The returned computed tabindex from IsFocusable() is as follows:
2736 // < 0 not tabbable at all
2737 // == 0 in normal tab order (last after positive tabindexed items)
2738 // > 0 can be tabbed to in the order specified by this value
2739
2740 int32_t tabIndex;
2741 frame->IsFocusable(&tabIndex, 0);
2742
2743 LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
2744 LOGFOCUSNAVIGATION((" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
2745
2746 nsIContent* currentContent = frame->GetContent();
2747 if (tabIndex >= 0) {
2748 NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
2749 if (currentContent->Tag() == nsGkAtoms::img &&
2750 currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
2751 // This is an image with a map. Image map areas are not traversed by
2752 // nsIFrameTraversal so look for the next or previous area element.
2753 nsIContent *areaContent =
2754 GetNextTabbableMapArea(aForward, aCurrentTabIndex,
2755 currentContent, iterStartContent);
2756 if (areaContent) {
2757 NS_ADDREF(*aResultContent = areaContent);
2758 return NS_OK;
2759 }
2760 }
2761 else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
2762 // break out if we've wrapped around to the start again.
2763 if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2764 NS_ADDREF(*aResultContent = currentContent);
2765 return NS_OK;
2766 }
2767
2768 // found a node with a matching tab index. Check if it is a child
2769 // frame. If so, navigate into the child frame instead.
2770 nsIDocument* doc = currentContent->GetCurrentDoc();
2771 NS_ASSERTION(doc, "content not in document");
2772 nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
2773 if (subdoc) {
2774 if (!subdoc->EventHandlingSuppressed()) {
2775 if (aForward) {
2776 // when tabbing forward into a frame, return the root
2777 // frame so that the canvas becomes focused.
2778 nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
2779 if (subframe) {
2780 // If the subframe body is editable by contenteditable,
2781 // we should set the editor's root element rather than the
2782 // actual root element. Otherwise, we should set the focus
2783 // to the root content.
2784 *aResultContent =
2785 nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
2786 if (!*aResultContent ||
2787 !((*aResultContent)->GetPrimaryFrame())) {
2788 *aResultContent =
2789 GetRootForFocus(subframe, subdoc, false, true);
2790 }
2791 if (*aResultContent) {
2792 NS_ADDREF(*aResultContent);
2793 return NS_OK;
2794 }
2795 }
2796 }
2797 Element* rootElement = subdoc->GetRootElement();
2798 nsIPresShell* subShell = subdoc->GetShell();
2799 if (rootElement && subShell) {
2800 rv = GetNextTabbableContent(subShell, rootElement,
2801 aOriginalStartContent, rootElement,
2802 aForward, (aForward ? 1 : 0),
2803 false, aResultContent);
2804 NS_ENSURE_SUCCESS(rv, rv);
2805 if (*aResultContent)
2806 return NS_OK;
2807 }
2808 }
2809 }
2810 // otherwise, use this as the next content node to tab to, unless
2811 // this was the element we started on. This would happen for
2812 // instance on an element with child frames, where frame navigation
2813 // could return the original element again. In that case, just skip
2814 // it. Also, if the next content node is the root content, then
2815 // return it. This latter case would happen only if someone made a
2816 // popup focusable.
2817 // Also, when going backwards, check to ensure that the focus
2818 // wouldn't be redirected. Otherwise, for example, when an input in
2819 // a textbox is focused, the enclosing textbox would be found and
2820 // the same inner input would be returned again.
2821 else if (currentContent == aRootContent ||
2822 (currentContent != startContent &&
2823 (aForward || !GetRedirectedFocus(currentContent)))) {
2824 NS_ADDREF(*aResultContent = currentContent);
2825 return NS_OK;
2826 }
2827 }
2828 }
2829 else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
2830 // not focusable, so return if we have wrapped around to the original
2831 // content. This is necessary in case the original starting content was
2832 // not focusable.
2833 NS_ADDREF(*aResultContent = currentContent);
2834 return NS_OK;
2835 }
2836
2837 // Move to the next or previous frame, but ignore continuation frames
2838 // since only the first frame should be involved in focusability.
2839 // Otherwise, a loop will occur in the following example:
2840 // <span tabindex="1">...<a/><a/>...</span>
2841 // where the text wraps onto multiple lines. Tabbing from the second
2842 // link can find one of the span's continuation frames between the link
2843 // and the end of the span, and the span would end up getting focused
2844 // again.
2845 do {
2846 if (aForward)
2847 frameTraversal->Next();
2848 else
2849 frameTraversal->Prev();
2850 frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2851 } while (frame && frame->GetPrevContinuation());
2852 }
2853
2854 // If already at lowest priority tab (0), end search completely.
2855 // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
2856 if (aCurrentTabIndex == (aForward ? 0 : 1)) {
2857 // if going backwards, the canvas should be focused once the beginning
2858 // has been reached.
2859 if (!aForward) {
2860 nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
2861 NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
2862 NS_IF_ADDREF(*aResultContent =
2863 GetRootForFocus(window, aRootContent->GetCurrentDoc(), false, true));
2864 }
2865 break;
2866 }
2867
2868 // continue looking for next highest priority tabindex
2869 aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
2870 startContent = iterStartContent = aRootContent;
2871 }
2872
2873 return NS_OK;
2874 }
2875
2876 nsIContent*
2877 nsFocusManager::GetNextTabbableMapArea(bool aForward,
2878 int32_t aCurrentTabIndex,
2879 nsIContent* aImageContent,
2880 nsIContent* aStartContent)
2881 {
2882 nsAutoString useMap;
2883 aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
2884
2885 nsCOMPtr<nsIDocument> doc = aImageContent->GetDocument();
2886 if (doc) {
2887 nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap);
2888 if (!mapContent)
2889 return nullptr;
2890 uint32_t count = mapContent->GetChildCount();
2891 // First see if the the start content is in this map
2892
2893 int32_t index = mapContent->IndexOf(aStartContent);
2894 int32_t tabIndex;
2895 if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
2896 tabIndex != aCurrentTabIndex)) {
2897 // If aStartContent is in this map we must start iterating past it.
2898 // We skip the case where aStartContent has tabindex == aStartContent
2899 // since the next tab ordered element might be before it
2900 // (or after for backwards) in the child list.
2901 index = aForward ? -1 : (int32_t)count;
2902 }
2903
2904 // GetChildAt will return nullptr if our index < 0 or index >= count
2905 nsCOMPtr<nsIContent> areaContent;
2906 while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nullptr) {
2907 if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
2908 return areaContent;
2909 }
2910 }
2911 }
2912
2913 return nullptr;
2914 }
2915
2916 int32_t
2917 nsFocusManager::GetNextTabIndex(nsIContent* aParent,
2918 int32_t aCurrentTabIndex,
2919 bool aForward)
2920 {
2921 int32_t tabIndex, childTabIndex;
2922
2923 if (aForward) {
2924 tabIndex = 0;
2925 for (nsIContent* child = aParent->GetFirstChild();
2926 child;
2927 child = child->GetNextSibling()) {
2928 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
2929 if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
2930 tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
2931 }
2932
2933 nsAutoString tabIndexStr;
2934 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
2935 nsresult ec;
2936 int32_t val = tabIndexStr.ToInteger(&ec);
2937 if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
2938 tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
2939 }
2940 }
2941 }
2942 else { /* !aForward */
2943 tabIndex = 1;
2944 for (nsIContent* child = aParent->GetFirstChild();
2945 child;
2946 child = child->GetNextSibling()) {
2947 childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
2948 if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
2949 (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
2950 tabIndex = childTabIndex;
2951 }
2952
2953 nsAutoString tabIndexStr;
2954 child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
2955 nsresult ec;
2956 int32_t val = tabIndexStr.ToInteger(&ec);
2957 if (NS_SUCCEEDED (ec)) {
2958 if ((aCurrentTabIndex == 0 && val > tabIndex) ||
2959 (val < aCurrentTabIndex && val > tabIndex) ) {
2960 tabIndex = val;
2961 }
2962 }
2963 }
2964 }
2965
2966 return tabIndex;
2967 }
2968
2969 nsIContent*
2970 nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
2971 nsIDocument* aDocument,
2972 bool aIsForDocNavigation,
2973 bool aCheckVisibility)
2974 {
2975 // the root element's canvas may be focused as long as the document is in a
2976 // a non-chrome shell and does not contain a frameset.
2977 if (aIsForDocNavigation) {
2978 nsCOMPtr<nsIContent> docContent =
2979 do_QueryInterface(aWindow->GetFrameElementInternal());
2980 // document navigation skips iframes and frames that are specifically non-focusable
2981 if (docContent) {
2982 if (docContent->Tag() == nsGkAtoms::iframe)
2983 return nullptr;
2984
2985 nsIFrame* frame = docContent->GetPrimaryFrame();
2986 if (!frame || !frame->IsFocusable(nullptr, 0))
2987 return nullptr;
2988 }
2989 } else {
2990 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2991 if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
2992 return nullptr;
2993 }
2994 }
2995
2996 if (aCheckVisibility && !IsWindowVisible(aWindow))
2997 return nullptr;
2998
2999 Element *rootElement = aDocument->GetRootElement();
3000 if (!rootElement) {
3001 return nullptr;
3002 }
3003
3004 if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
3005 return nullptr;
3006 }
3007
3008 // Finally, check if this is a frameset
3009 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
3010 if (htmlDoc && aDocument->GetHtmlChildElement(nsGkAtoms::frameset)) {
3011 return nullptr;
3012 }
3013
3014 return rootElement;
3015 }
3016
3017 void
3018 nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
3019 nsIDocShellTreeItem** aResult)
3020 {
3021 *aResult = nullptr;
3022
3023 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
3024 while (curItem) {
3025 int32_t childCount = 0;
3026 curItem->GetChildCount(&childCount);
3027 if (!childCount) {
3028 *aResult = curItem;
3029 NS_ADDREF(*aResult);
3030 return;
3031 }
3032
3033
3034 curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
3035 }
3036 }
3037
3038 void
3039 nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
3040 nsIDocShellTreeItem** aResult)
3041 {
3042 *aResult = nullptr;
3043
3044 int32_t childCount = 0;
3045 aItem->GetChildCount(&childCount);
3046 if (childCount) {
3047 aItem->GetChildAt(0, aResult);
3048 if (*aResult)
3049 return;
3050 }
3051
3052 nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
3053 while (curItem) {
3054 nsCOMPtr<nsIDocShellTreeItem> parentItem;
3055 curItem->GetParent(getter_AddRefs(parentItem));
3056 if (!parentItem)
3057 return;
3058
3059 // Note that we avoid using GetChildOffset() here because docshell
3060 // child offsets can't be trusted to be correct. bug 162283.
3061 nsCOMPtr<nsIDocShellTreeItem> iterItem;
3062 childCount = 0;
3063 parentItem->GetChildCount(&childCount);
3064 for (int32_t index = 0; index < childCount; ++index) {
3065 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
3066 if (iterItem == curItem) {
3067 ++index;
3068 if (index < childCount) {
3069 parentItem->GetChildAt(index, aResult);
3070 if (*aResult)
3071 return;
3072 }
3073 break;
3074 }
3075 }
3076
3077 curItem = parentItem;
3078 }
3079 }
3080
3081 void
3082 nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
3083 nsIDocShellTreeItem** aResult)
3084 {
3085 *aResult = nullptr;
3086
3087 nsCOMPtr<nsIDocShellTreeItem> parentItem;
3088 aItem->GetParent(getter_AddRefs(parentItem));
3089 if (!parentItem)
3090 return;
3091
3092 // Note that we avoid using GetChildOffset() here because docshell
3093 // child offsets can't be trusted to be correct. bug 162283.
3094 int32_t childCount = 0;
3095 parentItem->GetChildCount(&childCount);
3096 nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
3097 for (int32_t index = 0; index < childCount; ++index) {
3098 parentItem->GetChildAt(index, getter_AddRefs(iterItem));
3099 if (iterItem == aItem)
3100 break;
3101 prevItem = iterItem;
3102 }
3103
3104 if (prevItem)
3105 GetLastDocShell(prevItem, aResult);
3106 else
3107 NS_ADDREF(*aResult = parentItem);
3108 }
3109
3110 nsIContent*
3111 nsFocusManager::GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward)
3112 {
3113 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
3114 if (!pm)
3115 return nullptr;
3116
3117 // Iterate through the array backwards if aForward is false.
3118 nsTArray<nsIFrame *> popups;
3119 pm->GetVisiblePopups(popups);
3120 int32_t i = aForward ? 0 : popups.Length() - 1;
3121 int32_t end = aForward ? popups.Length() : -1;
3122
3123 for (; i != end; aForward ? i++ : i--) {
3124 nsIFrame* popupFrame = popups[i];
3125 if (aCurrentPopup) {
3126 // If the current popup is set, then we need to skip over this popup and
3127 // wait until the currently focused popup is found. Once found, the
3128 // current popup will be cleared so that the next popup is used.
3129 if (aCurrentPopup == popupFrame)
3130 aCurrentPopup = nullptr;
3131 continue;
3132 }
3133
3134 // Skip over non-panels
3135 if (popupFrame->GetContent()->Tag() != nsGkAtoms::panel ||
3136 (aDocument && popupFrame->GetContent()->GetCurrentDoc() != aDocument)) {
3137 continue;
3138 }
3139
3140 // Find the first focusable content within the popup. If there isn't any
3141 // focusable content in the popup, skip to the next popup.
3142 nsIPresShell* presShell = popupFrame->PresContext()->GetPresShell();
3143 if (presShell) {
3144 nsCOMPtr<nsIContent> nextFocus;
3145 nsIContent* popup = popupFrame->GetContent();
3146 nsresult rv = GetNextTabbableContent(presShell, popup,
3147 nullptr, popup,
3148 true, 1, false,
3149 getter_AddRefs(nextFocus));
3150 if (NS_SUCCEEDED(rv) && nextFocus) {
3151 return nextFocus.get();
3152 }
3153 }
3154 }
3155
3156 return nullptr;
3157 }
3158
3159 nsIContent*
3160 nsFocusManager::GetNextTabbableDocument(nsIContent* aStartContent, bool aForward)
3161 {
3162 // If currentPopup is set, then the starting content is in a panel.
3163 nsIFrame* currentPopup = nullptr;
3164 nsCOMPtr<nsIDocument> doc;
3165 nsCOMPtr<nsIDocShell> startDocShell;
3166
3167 if (aStartContent) {
3168 doc = aStartContent->GetCurrentDoc();
3169 if (doc) {
3170 startDocShell = doc->GetWindow()->GetDocShell();
3171 }
3172
3173 // Check if the starting content is inside a panel. Document navigation
3174 // must start from this panel instead of the document root.
3175 nsIContent* content = aStartContent;
3176 while (content) {
3177 if (content->NodeInfo()->Equals(nsGkAtoms::panel, kNameSpaceID_XUL)) {
3178 currentPopup = content->GetPrimaryFrame();
3179 break;
3180 }
3181 content = content->GetParent();
3182 }
3183 }
3184 else if (mFocusedWindow) {
3185 startDocShell = mFocusedWindow->GetDocShell();
3186 doc = mFocusedWindow->GetExtantDoc();
3187 }
3188 else {
3189 nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(mActiveWindow);
3190 startDocShell = do_QueryInterface(webnav);
3191
3192 if (mActiveWindow) {
3193 doc = mActiveWindow->GetExtantDoc();
3194 }
3195 }
3196
3197 if (!startDocShell)
3198 return nullptr;
3199
3200 // perform a depth first search (preorder) of the docshell tree
3201 // looking for an HTML Frame or a chrome document
3202 nsIContent* content = aStartContent;
3203 nsCOMPtr<nsIDocShellTreeItem> curItem = startDocShell.get();
3204 nsCOMPtr<nsIDocShellTreeItem> nextItem;
3205 do {
3206 // If moving forward, check for a panel in the starting document. If one
3207 // exists with focusable content, return that content instead of the next
3208 // document. If currentPopup is set, then, another panel may exist. If no
3209 // such panel exists, then continue on to check the next document.
3210 // When moving backwards, and the starting content is in a panel, then
3211 // check for additional panels in the starting document. If the starting
3212 // content is not in a panel, move back to the previous document and check
3213 // for panels there.
3214
3215 bool checkPopups = false;
3216 nsCOMPtr<nsPIDOMWindow> nextFrame = nullptr;
3217
3218 if (doc && (aForward || currentPopup)) {
3219 nsIContent* popupContent = GetNextTabbablePanel(doc, currentPopup, aForward);
3220 if (popupContent)
3221 return popupContent;
3222
3223 if (!aForward && currentPopup) {
3224 // The starting content was in a popup, yet no other popups were
3225 // found. Move onto the starting content's document.
3226 nextFrame = doc->GetWindow();
3227 }
3228 }
3229
3230 // Look for the next or previous document.
3231 if (!nextFrame) {
3232 if (aForward) {
3233 GetNextDocShell(curItem, getter_AddRefs(nextItem));
3234 if (!nextItem) {
3235 // wrap around to the beginning, which is the top of the tree
3236 startDocShell->GetRootTreeItem(getter_AddRefs(nextItem));
3237 }
3238 }
3239 else {
3240 GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
3241 if (!nextItem) {
3242 // wrap around to the end, which is the last item in the tree
3243 nsCOMPtr<nsIDocShellTreeItem> rootItem;
3244 startDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
3245 GetLastDocShell(rootItem, getter_AddRefs(nextItem));
3246 }
3247
3248 // When going back to the previous document, check for any focusable
3249 // popups in that previous document first.
3250 checkPopups = true;
3251 }
3252
3253 curItem = nextItem;
3254 nextFrame = do_GetInterface(nextItem);
3255 }
3256
3257 if (!nextFrame)
3258 return nullptr;
3259
3260 // Clear currentPopup for the next iteration
3261 currentPopup = nullptr;
3262
3263 // If event handling is suppressed, move on to the next document. Set
3264 // content to null so that the popup check will be skipped on the next
3265 // loop iteration.
3266 doc = nextFrame->GetExtantDoc();
3267 if (!doc || doc->EventHandlingSuppressed()) {
3268 content = nullptr;
3269 continue;
3270 }
3271
3272 if (checkPopups) {
3273 // When iterating backwards, check the panels of the previous document
3274 // first. If a panel exists that has focusable content, focus that.
3275 // Otherwise, continue on to focus the document.
3276 nsIContent* popupContent = GetNextTabbablePanel(doc, nullptr, false);
3277 if (popupContent)
3278 return popupContent;
3279 }
3280
3281 content = GetRootForFocus(nextFrame, doc, true, true);
3282 if (content && !GetRootForFocus(nextFrame, doc, false, false)) {
3283 // if the found content is in a chrome shell or a frameset, navigate
3284 // forward one tabbable item so that the first item is focused. Note
3285 // that we always go forward and not back here.
3286 nsCOMPtr<nsIContent> nextFocus;
3287 Element* rootElement = doc->GetRootElement();
3288 nsIPresShell* presShell = doc->GetShell();
3289 if (presShell) {
3290 nsresult rv = GetNextTabbableContent(presShell, rootElement,
3291 nullptr, rootElement,
3292 true, 1, false,
3293 getter_AddRefs(nextFocus));
3294 return NS_SUCCEEDED(rv) ? nextFocus.get() : nullptr;
3295 }
3296 }
3297
3298 } while (!content);
3299
3300 return content;
3301 }
3302
3303 void
3304 nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
3305 nsIContent* aStartSelection,
3306 nsIContent* aEndSelection,
3307 nsIContent** aFocusedContent)
3308 {
3309 *aFocusedContent = nullptr;
3310
3311 nsCOMPtr<nsIContent> testContent = aStartSelection;
3312 nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
3313
3314 nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode();
3315
3316 // We now have the correct start node in selectionContent!
3317 // Search for focusable elements, starting with selectionContent
3318
3319 // Method #1: Keep going up while we look - an ancestor might be focusable
3320 // We could end the loop earlier, such as when we're no longer
3321 // in the same frame, by comparing selectionContent->GetPrimaryFrame()
3322 // with a variable holding the starting selectionContent
3323 while (testContent) {
3324 // Keep testing while selectionContent is equal to something,
3325 // eventually we'll run out of ancestors
3326
3327 nsCOMPtr<nsIURI> uri;
3328 if (testContent == currentFocus ||
3329 testContent->IsLink(getter_AddRefs(uri))) {
3330 NS_ADDREF(*aFocusedContent = testContent);
3331 return;
3332 }
3333
3334 // Get the parent
3335 testContent = testContent->GetParent();
3336
3337 if (!testContent) {
3338 // We run this loop again, checking the ancestor chain of the selection's end point
3339 testContent = nextTestContent;
3340 nextTestContent = nullptr;
3341 }
3342 }
3343
3344 // We couldn't find an anchor that was an ancestor of the selection start
3345 // Method #2: look for anchor in selection's primary range (depth first search)
3346
3347 // Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
3348 nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
3349 nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
3350 nsCOMPtr<nsIDOMNode> testNode;
3351
3352 do {
3353 testContent = do_QueryInterface(selectionNode);
3354
3355 // We're looking for any focusable link that could be part of the
3356 // main document's selection.
3357 nsCOMPtr<nsIURI> uri;
3358 if (testContent == currentFocus ||
3359 testContent->IsLink(getter_AddRefs(uri))) {
3360 NS_ADDREF(*aFocusedContent = testContent);
3361 return;
3362 }
3363
3364 selectionNode->GetFirstChild(getter_AddRefs(testNode));
3365 if (testNode) {
3366 selectionNode = testNode;
3367 continue;
3368 }
3369
3370 if (selectionNode == endSelectionNode)
3371 break;
3372 selectionNode->GetNextSibling(getter_AddRefs(testNode));
3373 if (testNode) {
3374 selectionNode = testNode;
3375 continue;
3376 }
3377
3378 do {
3379 selectionNode->GetParentNode(getter_AddRefs(testNode));
3380 if (!testNode || testNode == endSelectionNode) {
3381 selectionNode = nullptr;
3382 break;
3383 }
3384 testNode->GetNextSibling(getter_AddRefs(selectionNode));
3385 if (selectionNode)
3386 break;
3387 selectionNode = testNode;
3388 } while (true);
3389 }
3390 while (selectionNode && selectionNode != endSelectionNode);
3391 }
3392
3393 class PointerUnlocker : public nsRunnable
3394 {
3395 public:
3396 PointerUnlocker()
3397 {
3398 MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
3399 PointerUnlocker::sActiveUnlocker = this;
3400 }
3401
3402 ~PointerUnlocker()
3403 {
3404 if (PointerUnlocker::sActiveUnlocker == this) {
3405 PointerUnlocker::sActiveUnlocker = nullptr;
3406 }
3407 }
3408
3409 NS_IMETHOD Run()
3410 {
3411 if (PointerUnlocker::sActiveUnlocker == this) {
3412 PointerUnlocker::sActiveUnlocker = nullptr;
3413 }
3414 NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
3415 nsPIDOMWindow* focused =
3416 nsFocusManager::GetFocusManager()->GetFocusedWindow();
3417 nsCOMPtr<nsIDocument> pointerLockedDoc =
3418 do_QueryReferent(EventStateManager::sPointerLockedDoc);
3419 if (pointerLockedDoc &&
3420 !nsContentUtils::IsInPointerLockContext(focused)) {
3421 nsIDocument::UnlockPointer();
3422 }
3423 return NS_OK;
3424 }
3425
3426 static PointerUnlocker* sActiveUnlocker;
3427 };
3428
3429 PointerUnlocker*
3430 PointerUnlocker::sActiveUnlocker = nullptr;
3431
3432 void
3433 nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindow* aWindow)
3434 {
3435 if (!PointerUnlocker::sActiveUnlocker &&
3436 nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
3437 !nsContentUtils::IsInPointerLockContext(aWindow)) {
3438 nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
3439 NS_DispatchToCurrentThread(runnable);
3440 }
3441 mFocusedWindow = aWindow;
3442 }
3443
3444 void
3445 nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration)
3446 {
3447 if (!sInstance) {
3448 return;
3449 }
3450
3451 if (sInstance->mActiveWindow) {
3452 sInstance->mActiveWindow->
3453 MarkUncollectableForCCGeneration(aGeneration);
3454 }
3455 if (sInstance->mFocusedWindow) {
3456 sInstance->mFocusedWindow->
3457 MarkUncollectableForCCGeneration(aGeneration);
3458 }
3459 if (sInstance->mWindowBeingLowered) {
3460 sInstance->mWindowBeingLowered->
3461 MarkUncollectableForCCGeneration(aGeneration);
3462 }
3463 if (sInstance->mFocusedContent) {
3464 sInstance->mFocusedContent->OwnerDoc()->
3465 MarkUncollectableForCCGeneration(aGeneration);
3466 }
3467 if (sInstance->mFirstBlurEvent) {
3468 sInstance->mFirstBlurEvent->OwnerDoc()->
3469 MarkUncollectableForCCGeneration(aGeneration);
3470 }
3471 if (sInstance->mFirstFocusEvent) {
3472 sInstance->mFirstFocusEvent->OwnerDoc()->
3473 MarkUncollectableForCCGeneration(aGeneration);
3474 }
3475 if (sInstance->mMouseDownEventHandlingDocument) {
3476 sInstance->mMouseDownEventHandlingDocument->
3477 MarkUncollectableForCCGeneration(aGeneration);
3478 }
3479 }
3480
3481 nsresult
3482 NS_NewFocusManager(nsIFocusManager** aResult)
3483 {
3484 NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
3485 return NS_OK;
3486 }

mercurial