|
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 } |