Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsFocusManager.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "mozilla/dom/TabParent.h"
8
9
#include "nsFocusManager.h"
10
11
#include "ChildIterator.h"
12
#include "nsIInterfaceRequestorUtils.h"
13
#include "nsGkAtoms.h"
14
#include "nsGlobalWindow.h"
15
#include "nsContentUtils.h"
16
#include "nsDocument.h"
17
#include "nsIContentParent.h"
18
#include "nsPIDOMWindow.h"
19
#include "nsIDOMChromeWindow.h"
20
#include "nsIHTMLDocument.h"
21
#include "nsIDocShell.h"
22
#include "nsIDocShellTreeOwner.h"
23
#include "nsIFormControl.h"
24
#include "nsLayoutUtils.h"
25
#include "nsIPresShell.h"
26
#include "nsFrameTraversal.h"
27
#include "nsIWebNavigation.h"
28
#include "nsCaret.h"
29
#include "nsIBaseWindow.h"
30
#include "nsIXULWindow.h"
31
#include "nsViewManager.h"
32
#include "nsFrameSelection.h"
33
#include "mozilla/dom/Selection.h"
34
#include "nsXULPopupManager.h"
35
#include "nsMenuPopupFrame.h"
36
#include "nsIScriptObjectPrincipal.h"
37
#include "nsIPrincipal.h"
38
#include "nsIObserverService.h"
39
#include "nsIObjectFrame.h"
40
#include "nsBindingManager.h"
41
#include "nsStyleCoord.h"
42
#include "TabChild.h"
43
#include "nsFrameLoader.h"
44
#include "nsNumberControlFrame.h"
45
#include "nsNetUtil.h"
46
#include "nsRange.h"
47
48
#include "mozilla/AccessibleCaretEventHub.h"
49
#include "mozilla/ContentEvents.h"
50
#include "mozilla/dom/Element.h"
51
#include "mozilla/dom/HTMLImageElement.h"
52
#include "mozilla/dom/HTMLInputElement.h"
53
#include "mozilla/dom/HTMLSlotElement.h"
54
#include "mozilla/dom/Text.h"
55
#include "mozilla/EventDispatcher.h"
56
#include "mozilla/EventStateManager.h"
57
#include "mozilla/EventStates.h"
58
#include "mozilla/HTMLEditor.h"
59
#include "mozilla/IMEStateManager.h"
60
#include "mozilla/LookAndFeel.h"
61
#include "mozilla/Preferences.h"
62
#include "mozilla/Services.h"
63
#include "mozilla/Unused.h"
64
#include <algorithm>
65
66
#ifdef MOZ_XUL
67
#include "nsIDOMXULMenuListElement.h"
68
#endif
69
70
#ifdef ACCESSIBILITY
71
#include "nsAccessibilityService.h"
72
#endif
73
74
#ifndef XP_MACOSX
75
#include "nsIScriptError.h"
76
#endif
77
78
using namespace mozilla;
79
using namespace mozilla::dom;
80
using namespace mozilla::widget;
81
82
// Two types of focus pr logging are available:
83
//   'Focus' for normal focus manager calls
84
//   'FocusNavigation' for tab and document navigation
85
LazyLogModule gFocusLog("Focus");
86
LazyLogModule gFocusNavigationLog("FocusNavigation");
87
88
0
#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
89
0
#define LOGFOCUSNAVIGATION(args) MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
90
91
#define LOGTAG(log, format, content)                            \
92
0
  if (MOZ_LOG_TEST(log, LogLevel::Debug)) {                         \
93
0
    nsAutoCString tag(NS_LITERAL_CSTRING("(none)"));            \
94
0
    if (content) {                                              \
95
0
      content->NodeInfo()->NameAtom()->ToUTF8String(tag);       \
96
0
    }                                                           \
97
0
    MOZ_LOG(log, LogLevel::Debug, (format, tag.get()));             \
98
0
  }
99
100
0
#define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
101
0
#define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content)
102
103
struct nsDelayedBlurOrFocusEvent
104
{
105
  nsDelayedBlurOrFocusEvent(EventMessage aEventMessage,
106
                            nsIPresShell* aPresShell,
107
                            nsIDocument* aDocument,
108
                            EventTarget* aTarget,
109
                            EventTarget* aRelatedTarget)
110
    : mPresShell(aPresShell)
111
    , mDocument(aDocument)
112
    , mTarget(aTarget)
113
    , mEventMessage(aEventMessage)
114
    , mRelatedTarget(aRelatedTarget)
115
0
 {
116
0
 }
117
118
  nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
119
    : mPresShell(aOther.mPresShell)
120
    , mDocument(aOther.mDocument)
121
    , mTarget(aOther.mTarget)
122
    , mEventMessage(aOther.mEventMessage)
123
0
  {
124
0
  }
125
126
  nsCOMPtr<nsIPresShell> mPresShell;
127
  nsCOMPtr<nsIDocument> mDocument;
128
  nsCOMPtr<EventTarget> mTarget;
129
  EventMessage mEventMessage;
130
  nsCOMPtr<EventTarget> mRelatedTarget;
131
};
132
133
inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField)
134
0
{
135
0
  aField.mPresShell = nullptr;
136
0
  aField.mDocument = nullptr;
137
0
  aField.mTarget = nullptr;
138
0
  aField.mRelatedTarget = nullptr;
139
0
}
140
141
inline void
142
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
143
                            nsDelayedBlurOrFocusEvent& aField,
144
                            const char* aName,
145
                            uint32_t aFlags = 0)
146
0
{
147
0
  CycleCollectionNoteChild(aCallback, aField.mPresShell.get(), aName, aFlags);
148
0
  CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags);
149
0
  CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags);
150
0
  CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName, aFlags);
151
0
}
152
153
3
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
154
3
  NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
155
3
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
156
3
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
157
3
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
158
0
NS_INTERFACE_MAP_END
159
160
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
161
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
162
163
NS_IMPL_CYCLE_COLLECTION(nsFocusManager,
164
                         mActiveWindow,
165
                         mFocusedWindow,
166
                         mFocusedElement,
167
                         mFirstBlurEvent,
168
                         mFirstFocusEvent,
169
                         mWindowBeingLowered,
170
                         mDelayedBlurFocusEvents,
171
                         mMouseButtonEventHandlingDocument)
172
173
nsFocusManager* nsFocusManager::sInstance = nullptr;
174
bool nsFocusManager::sMouseFocusesFormControl = false;
175
bool nsFocusManager::sTestMode = false;
176
177
static const char* kObservedPrefs[] = {
178
  "accessibility.browsewithcaret",
179
  "accessibility.tabfocus_applies_to_xul",
180
  "accessibility.mouse_focuses_formcontrol",
181
  "focusmanager.testmode",
182
  nullptr
183
};
184
185
nsFocusManager::nsFocusManager()
186
  : mEventHandlingNeedsFlush(false)
187
3
{ }
188
189
nsFocusManager::~nsFocusManager()
190
0
{
191
0
  Preferences::UnregisterCallbacks(
192
0
    PREF_CHANGE_METHOD(nsFocusManager::PrefChanged),
193
0
    kObservedPrefs, this);
194
0
195
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
196
0
  if (obs) {
197
0
    obs->RemoveObserver(this, "xpcom-shutdown");
198
0
  }
199
0
}
200
201
// static
202
nsresult
203
nsFocusManager::Init()
204
3
{
205
3
  nsFocusManager* fm = new nsFocusManager();
206
3
  NS_ADDREF(fm);
207
3
  sInstance = fm;
208
3
209
3
  nsIContent::sTabFocusModelAppliesToXUL =
210
3
    Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
211
3
                         nsIContent::sTabFocusModelAppliesToXUL);
212
3
213
3
  sMouseFocusesFormControl =
214
3
    Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
215
3
216
3
  sTestMode = Preferences::GetBool("focusmanager.testmode", false);
217
3
218
3
  Preferences::RegisterCallbacks(
219
3
    PREF_CHANGE_METHOD(nsFocusManager::PrefChanged),
220
3
    kObservedPrefs, fm);
221
3
222
3
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
223
3
  if (obs) {
224
3
    obs->AddObserver(fm, "xpcom-shutdown", true);
225
3
  }
226
3
227
3
  return NS_OK;
228
3
}
229
230
// static
231
void
232
nsFocusManager::Shutdown()
233
0
{
234
0
  NS_IF_RELEASE(sInstance);
235
0
}
236
237
void
238
nsFocusManager::PrefChanged(const char* aPref)
239
0
{
240
0
  nsDependentCString pref(aPref);
241
0
  if (pref.EqualsLiteral("accessibility.browsewithcaret")) {
242
0
    UpdateCaretForCaretBrowsingMode();
243
0
  }
244
0
  else if (pref.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
245
0
    nsIContent::sTabFocusModelAppliesToXUL =
246
0
      Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
247
0
                           nsIContent::sTabFocusModelAppliesToXUL);
248
0
  }
249
0
  else if (pref.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
250
0
    sMouseFocusesFormControl =
251
0
      Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
252
0
                           false);
253
0
  }
254
0
  else if (pref.EqualsLiteral("focusmanager.testmode")) {
255
0
    sTestMode = Preferences::GetBool("focusmanager.testmode", false);
256
0
  }
257
0
}
258
259
NS_IMETHODIMP
260
nsFocusManager::Observe(nsISupports *aSubject,
261
                        const char *aTopic,
262
                        const char16_t *aData)
263
0
{
264
0
  if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
265
0
    mActiveWindow = nullptr;
266
0
    mFocusedWindow = nullptr;
267
0
    mFocusedElement = nullptr;
268
0
    mFirstBlurEvent = nullptr;
269
0
    mFirstFocusEvent = nullptr;
270
0
    mWindowBeingLowered = nullptr;
271
0
    mDelayedBlurFocusEvents.Clear();
272
0
    mMouseButtonEventHandlingDocument = nullptr;
273
0
  }
274
0
275
0
  return NS_OK;
276
0
}
277
278
// given a frame content node, retrieve the nsIDOMWindow displayed in it
279
static nsPIDOMWindowOuter*
280
GetContentWindow(nsIContent* aContent)
281
0
{
282
0
  nsIDocument* doc = aContent->GetComposedDoc();
283
0
  if (doc) {
284
0
    nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
285
0
    if (subdoc)
286
0
      return subdoc->GetWindow();
287
0
  }
288
0
289
0
  return nullptr;
290
0
}
291
292
bool
293
nsFocusManager::IsFocused(nsIContent* aContent)
294
0
{
295
0
  if (!aContent || !mFocusedElement) {
296
0
    return false;
297
0
  }
298
0
  return aContent == mFocusedElement;
299
0
}
300
301
bool
302
nsFocusManager::IsTestMode()
303
0
{
304
0
  return sTestMode;
305
0
}
306
307
// get the current window for the given content node
308
static nsPIDOMWindowOuter*
309
GetCurrentWindow(nsIContent* aContent)
310
0
{
311
0
  nsIDocument* doc = aContent->GetComposedDoc();
312
0
  return doc ? doc->GetWindow() : nullptr;
313
0
}
314
315
// static
316
Element*
317
nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow,
318
                                     SearchRange aSearchRange,
319
                                     nsPIDOMWindowOuter** aFocusedWindow)
320
0
{
321
0
  NS_ENSURE_TRUE(aWindow, nullptr);
322
0
323
0
  *aFocusedWindow = nullptr;
324
0
325
0
  Element* currentElement = nullptr;
326
0
  nsPIDOMWindowOuter* window = aWindow;
327
0
  for (;;) {
328
0
    *aFocusedWindow = window;
329
0
    currentElement = window->GetFocusedElement();
330
0
    if (!currentElement || aSearchRange == eOnlyCurrentWindow) {
331
0
      break;
332
0
    }
333
0
334
0
    window = GetContentWindow(currentElement);
335
0
    if (!window) {
336
0
      break;
337
0
    }
338
0
339
0
    if (aSearchRange == eIncludeAllDescendants) {
340
0
      continue;
341
0
    }
342
0
343
0
    MOZ_ASSERT(aSearchRange == eIncludeVisibleDescendants);
344
0
345
0
    // If the child window doesn't have PresShell, it means the window is
346
0
    // invisible.
347
0
    nsIDocShell* docShell = window->GetDocShell();
348
0
    if (!docShell) {
349
0
      break;
350
0
    }
351
0
    nsIPresShell* presShell = docShell->GetPresShell();
352
0
    if (!presShell) {
353
0
      break;
354
0
    }
355
0
  }
356
0
357
0
  NS_IF_ADDREF(*aFocusedWindow);
358
0
359
0
  return currentElement;
360
0
}
361
362
// static
363
Element*
364
nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
365
0
{
366
0
  // For input number, redirect focus to our anonymous text control.
367
0
  if (aContent->IsHTMLElement(nsGkAtoms::input)) {
368
0
    bool typeIsNumber =
369
0
      static_cast<dom::HTMLInputElement*>(aContent)->ControlType() ==
370
0
        NS_FORM_INPUT_NUMBER;
371
0
372
0
    if (typeIsNumber) {
373
0
      nsNumberControlFrame* numberControlFrame =
374
0
        do_QueryFrame(aContent->GetPrimaryFrame());
375
0
376
0
      if (numberControlFrame) {
377
0
        HTMLInputElement* textControl =
378
0
          numberControlFrame->GetAnonTextControl();
379
0
        return textControl;
380
0
      }
381
0
    }
382
0
  }
383
0
384
0
#ifdef MOZ_XUL
385
0
  if (aContent->IsXULElement()) {
386
0
    if (aContent->IsXULElement(nsGkAtoms::textbox)) {
387
0
      return aContent->OwnerDoc()->
388
0
        GetAnonymousElementByAttribute(aContent, nsGkAtoms::anonid, NS_LITERAL_STRING("input"));
389
0
    }
390
0
    else {
391
0
      nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
392
0
      if (menulist) {
393
0
        RefPtr<Element> inputField;
394
0
        menulist->GetInputField(getter_AddRefs(inputField));
395
0
        return inputField;
396
0
      }
397
0
    }
398
0
  }
399
0
#endif
400
0
401
0
  return nullptr;
402
0
}
403
404
// static
405
InputContextAction::Cause
406
nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags)
407
0
{
408
0
  if (aFlags & nsIFocusManager::FLAG_BYTOUCH) {
409
0
    return InputContextAction::CAUSE_TOUCH;
410
0
  } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
411
0
    return InputContextAction::CAUSE_MOUSE;
412
0
  } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
413
0
    return InputContextAction::CAUSE_KEY;
414
0
  }
415
0
  return InputContextAction::CAUSE_UNKNOWN;
416
0
}
417
418
NS_IMETHODIMP
419
nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow)
420
0
{
421
0
  NS_IF_ADDREF(*aWindow = mActiveWindow);
422
0
  return NS_OK;
423
0
}
424
425
NS_IMETHODIMP
426
nsFocusManager::SetActiveWindow(mozIDOMWindowProxy* aWindow)
427
0
{
428
0
  NS_ENSURE_STATE(aWindow);
429
0
430
0
  // only top-level windows can be made active
431
0
  nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aWindow);
432
0
  NS_ENSURE_TRUE(piWindow == piWindow->GetPrivateRoot(), NS_ERROR_INVALID_ARG);
433
0
434
0
  RaiseWindow(piWindow);
435
0
  return NS_OK;
436
0
}
437
438
NS_IMETHODIMP
439
nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow)
440
0
{
441
0
  NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
442
0
  return NS_OK;
443
0
}
444
445
NS_IMETHODIMP nsFocusManager::SetFocusedWindow(mozIDOMWindowProxy* aWindowToFocus)
446
0
{
447
0
  LOGFOCUS(("<<SetFocusedWindow begin>>"));
448
0
449
0
  nsCOMPtr<nsPIDOMWindowOuter> windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus);
450
0
  NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
451
0
452
0
  windowToFocus = windowToFocus->GetOuterWindow();
453
0
454
0
  nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
455
0
  if (frameElement) {
456
0
    // pass false for aFocusChanged so that the caret does not get updated
457
0
    // and scrolling does not occur.
458
0
    SetFocusInner(frameElement, 0, false, true);
459
0
  }
460
0
  else {
461
0
    // this is a top-level window. If the window has a child frame focused,
462
0
    // clear the focus. Otherwise, focus should already be in this frame, or
463
0
    // already cleared. This ensures that focus will be in this frame and not
464
0
    // in a child.
465
0
    nsIContent* content = windowToFocus->GetFocusedElement();
466
0
    if (content) {
467
0
      if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
468
0
        ClearFocus(windowToFocus);
469
0
    }
470
0
  }
471
0
472
0
  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
473
0
  if (rootWindow)
474
0
    RaiseWindow(rootWindow);
475
0
476
0
  LOGFOCUS(("<<SetFocusedWindow end>>"));
477
0
478
0
  return NS_OK;
479
0
}
480
481
NS_IMETHODIMP
482
nsFocusManager::GetFocusedElement(Element** aFocusedElement)
483
0
{
484
0
  RefPtr<Element> focusedElement = mFocusedElement;
485
0
  focusedElement.forget(aFocusedElement);
486
0
  return NS_OK;
487
0
}
488
489
NS_IMETHODIMP
490
nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow, uint32_t* aLastFocusMethod)
491
0
{
492
0
  // the focus method is stored on the inner window
493
0
  nsCOMPtr<nsPIDOMWindowOuter> window;
494
0
  if (aWindow) {
495
0
    window = nsPIDOMWindowOuter::From(aWindow);
496
0
  }
497
0
  if (!window)
498
0
    window = mFocusedWindow;
499
0
500
0
  *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
501
0
502
0
  NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
503
0
               "invalid focus method");
504
0
  return NS_OK;
505
0
}
506
507
NS_IMETHODIMP
508
nsFocusManager::SetFocus(Element* aElement, uint32_t aFlags)
509
0
{
510
0
  LOGFOCUS(("<<SetFocus begin>>"));
511
0
512
0
  NS_ENSURE_ARG(aElement);
513
0
514
0
  SetFocusInner(aElement, aFlags, true, true);
515
0
516
0
  LOGFOCUS(("<<SetFocus end>>"));
517
0
518
0
  return NS_OK;
519
0
}
520
521
NS_IMETHODIMP
522
nsFocusManager::ElementIsFocusable(Element* aElement, uint32_t aFlags,
523
                                   bool* aIsFocusable)
524
0
{
525
0
  NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
526
0
527
0
  *aIsFocusable = CheckIfFocusable(aElement, aFlags) != nullptr;
528
0
529
0
  return NS_OK;
530
0
}
531
532
NS_IMETHODIMP
533
nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, Element* aStartElement,
534
                          uint32_t aType, uint32_t aFlags, Element** aElement)
535
0
{
536
0
  *aElement = nullptr;
537
0
538
0
  LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
539
0
540
0
  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
541
0
    nsIDocument* doc = mFocusedWindow->GetExtantDoc();
542
0
    if (doc && doc->GetDocumentURI()) {
543
0
      LOGFOCUS((" Focused Window: %p %s",
544
0
                mFocusedWindow.get(),
545
0
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
546
0
    }
547
0
  }
548
0
549
0
  LOGCONTENT("  Current Focus: %s", mFocusedElement.get());
550
0
551
0
  // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
552
0
  // the other focus methods is already set, or we're just moving to the root
553
0
  // or caret position.
554
0
  if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
555
0
      (aFlags & FOCUSMETHOD_MASK) == 0) {
556
0
    aFlags |= FLAG_BYMOVEFOCUS;
557
0
  }
558
0
559
0
  nsCOMPtr<nsPIDOMWindowOuter> window;
560
0
  if (aStartElement) {
561
0
    window = GetCurrentWindow(aStartElement);
562
0
  } else {
563
0
    window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
564
0
  }
565
0
566
0
  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
567
0
568
0
  bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
569
0
  nsCOMPtr<nsIContent> newFocus;
570
0
  nsresult rv = DetermineElementToMoveFocus(window, aStartElement, aType, noParentTraversal,
571
0
                                            getter_AddRefs(newFocus));
572
0
  if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
573
0
    return NS_OK;
574
0
  }
575
0
576
0
  NS_ENSURE_SUCCESS(rv, rv);
577
0
578
0
  LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
579
0
580
0
  if (newFocus && newFocus->IsElement()) {
581
0
    // for caret movement, pass false for the aFocusChanged argument,
582
0
    // otherwise the caret will end up moving to the focus position. This
583
0
    // would be a problem because the caret would move to the beginning of the
584
0
    // focused link making it impossible to navigate the caret over a link.
585
0
    SetFocusInner(newFocus->AsElement(), aFlags, aType != MOVEFOCUS_CARET,
586
0
                  true);
587
0
    *aElement = do_AddRef(newFocus->AsElement()).take();
588
0
  }
589
0
  else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
590
0
    // no content was found, so clear the focus for these two types.
591
0
    ClearFocus(window);
592
0
  }
593
0
594
0
  LOGFOCUS(("<<MoveFocus end>>"));
595
0
596
0
  return NS_OK;
597
0
}
598
599
NS_IMETHODIMP
600
nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow)
601
0
{
602
0
  LOGFOCUS(("<<ClearFocus begin>>"));
603
0
604
0
  // if the window to clear is the focused window or an ancestor of the
605
0
  // focused window, then blur the existing focused content. Otherwise, the
606
0
  // focus is somewhere else so just update the current node.
607
0
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
608
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
609
0
610
0
  if (IsSameOrAncestor(window, mFocusedWindow)) {
611
0
    bool isAncestor = (window != mFocusedWindow);
612
0
    if (Blur(window, nullptr, isAncestor, true)) {
613
0
      // if we are clearing the focus on an ancestor of the focused window,
614
0
      // the ancestor will become the new focused window, so focus it
615
0
      if (isAncestor)
616
0
        Focus(window, nullptr, 0, true, false, false, true);
617
0
    }
618
0
  }
619
0
  else {
620
0
    window->SetFocusedElement(nullptr);
621
0
  }
622
0
623
0
  LOGFOCUS(("<<ClearFocus end>>"));
624
0
625
0
  return NS_OK;
626
0
}
627
628
NS_IMETHODIMP
629
nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
630
                                           bool aDeep,
631
                                           mozIDOMWindowProxy** aFocusedWindow,
632
                                           Element** aElement)
633
0
{
634
0
  *aElement = nullptr;
635
0
  if (aFocusedWindow)
636
0
    *aFocusedWindow = nullptr;
637
0
638
0
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
639
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
640
0
641
0
  nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
642
0
  RefPtr<Element> focusedElement =
643
0
    GetFocusedDescendant(window,
644
0
                         aDeep ? nsFocusManager::eIncludeAllDescendants :
645
0
                                 nsFocusManager::eOnlyCurrentWindow,
646
0
                         getter_AddRefs(focusedWindow));
647
0
648
0
  focusedElement.forget(aElement);
649
0
650
0
  if (aFocusedWindow)
651
0
    NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
652
0
653
0
  return NS_OK;
654
0
}
655
656
NS_IMETHODIMP
657
nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow)
658
0
{
659
0
  nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
660
0
  nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
661
0
  if (dsti) {
662
0
    if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
663
0
      nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
664
0
      NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
665
0
666
0
      // don't move the caret for editable documents
667
0
      bool isEditable;
668
0
      docShell->GetEditable(&isEditable);
669
0
      if (isEditable)
670
0
        return NS_OK;
671
0
672
0
      nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
673
0
      NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
674
0
675
0
      nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
676
0
      nsCOMPtr<nsIContent> content = window->GetFocusedElement();
677
0
      if (content)
678
0
        MoveCaretToFocus(presShell, content);
679
0
    }
680
0
  }
681
0
682
0
  return NS_OK;
683
0
}
684
685
NS_IMETHODIMP
686
nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow)
687
0
{
688
0
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
689
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
690
0
691
0
  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
692
0
    LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
693
0
    nsIDocument* doc = window->GetExtantDoc();
694
0
    if (doc && doc->GetDocumentURI()) {
695
0
      LOGFOCUS(("  Raised Window: %p %s", aWindow,
696
0
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
697
0
    }
698
0
    if (mActiveWindow) {
699
0
      doc = mActiveWindow->GetExtantDoc();
700
0
      if (doc && doc->GetDocumentURI()) {
701
0
        LOGFOCUS(("  Active Window: %p %s", mActiveWindow.get(),
702
0
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
703
0
      }
704
0
    }
705
0
  }
706
0
707
0
  if (mActiveWindow == window) {
708
0
    // The window is already active, so there is no need to focus anything,
709
0
    // but make sure that the right widget is focused. This is a special case
710
0
    // for Windows because when restoring a minimized window, a second
711
0
    // activation will occur and the top-level widget could be focused instead
712
0
    // of the child we want. We solve this by calling SetFocus to ensure that
713
0
    // what the focus manager thinks should be the current widget is actually
714
0
    // focused.
715
0
    EnsureCurrentWidgetFocused();
716
0
    return NS_OK;
717
0
  }
718
0
719
0
  // lower the existing window, if any. This shouldn't happen usually.
720
0
  if (mActiveWindow)
721
0
    WindowLowered(mActiveWindow);
722
0
723
0
  nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
724
0
  // If there's no docShellAsItem, this window must have been closed,
725
0
  // in that case there is no tree owner.
726
0
  NS_ENSURE_TRUE(docShellAsItem, NS_OK);
727
0
728
0
  // set this as the active window
729
0
  mActiveWindow = window;
730
0
731
0
  // ensure that the window is enabled and visible
732
0
  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
733
0
  docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
734
0
  nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
735
0
  if (baseWindow) {
736
0
    bool isEnabled = true;
737
0
    if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
738
0
      return NS_ERROR_FAILURE;
739
0
    }
740
0
741
0
    baseWindow->SetVisibility(true);
742
0
  }
743
0
744
0
  // If this is a parent or single process window, send the activate event.
745
0
  // Events for child process windows will be sent when ParentActivated
746
0
  // is called.
747
0
  if (XRE_IsParentProcess()) {
748
0
    ActivateOrDeactivate(window, true);
749
0
  }
750
0
751
0
  // retrieve the last focused element within the window that was raised
752
0
  nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
753
0
  RefPtr<Element> currentFocus =
754
0
    GetFocusedDescendant(window, eIncludeAllDescendants,
755
0
                         getter_AddRefs(currentWindow));
756
0
757
0
  NS_ASSERTION(currentWindow, "window raised with no window current");
758
0
  if (!currentWindow)
759
0
    return NS_OK;
760
0
761
0
  // If there is no nsIXULWindow, then this is an embedded or child process window.
762
0
  // Pass false for aWindowRaised so that commands get updated.
763
0
  nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(baseWindow));
764
0
  Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true);
765
0
766
0
  return NS_OK;
767
0
}
768
769
NS_IMETHODIMP
770
nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow)
771
0
{
772
0
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
773
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
774
0
775
0
  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
776
0
    LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
777
0
    nsIDocument* doc = window->GetExtantDoc();
778
0
    if (doc && doc->GetDocumentURI()) {
779
0
      LOGFOCUS(("  Lowered Window: %s",
780
0
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
781
0
    }
782
0
    if (mActiveWindow) {
783
0
      doc = mActiveWindow->GetExtantDoc();
784
0
      if (doc && doc->GetDocumentURI()) {
785
0
        LOGFOCUS(("  Active Window: %s",
786
0
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
787
0
      }
788
0
    }
789
0
  }
790
0
791
0
  if (mActiveWindow != window)
792
0
    return NS_OK;
793
0
794
0
  // clear the mouse capture as the active window has changed
795
0
  nsIPresShell::SetCapturingContent(nullptr, 0);
796
0
797
0
  // In addition, reset the drag state to ensure that we are no longer in
798
0
  // drag-select mode.
799
0
  if (mFocusedWindow) {
800
0
    nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
801
0
    if (docShell) {
802
0
      nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
803
0
      if (presShell) {
804
0
        RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
805
0
        frameSelection->SetDragState(false);
806
0
      }
807
0
    }
808
0
  }
809
0
810
0
  // If this is a parent or single process window, send the deactivate event.
811
0
  // Events for child process windows will be sent when ParentActivated
812
0
  // is called.
813
0
  if (XRE_IsParentProcess()) {
814
0
    ActivateOrDeactivate(window, false);
815
0
  }
816
0
817
0
  // keep track of the window being lowered, so that attempts to raise the
818
0
  // window can be prevented until we return. Otherwise, focus can get into
819
0
  // an unusual state.
820
0
  mWindowBeingLowered = mActiveWindow;
821
0
  mActiveWindow = nullptr;
822
0
823
0
  if (mFocusedWindow)
824
0
    Blur(nullptr, nullptr, true, true);
825
0
826
0
  mWindowBeingLowered = nullptr;
827
0
828
0
  return NS_OK;
829
0
}
830
831
nsresult
832
nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
833
0
{
834
0
  NS_ENSURE_ARG(aDocument);
835
0
  NS_ENSURE_ARG(aContent);
836
0
837
0
  nsPIDOMWindowOuter *window = aDocument->GetWindow();
838
0
  if (!window)
839
0
    return NS_OK;
840
0
841
0
  // if the content is currently focused in the window, or is an
842
0
  // shadow-including inclusive ancestor of the currently focused element,
843
0
  // reset the focus within that window.
844
0
  nsIContent* content = window->GetFocusedElement();
845
0
  if (content && nsContentUtils::ContentIsHostIncludingDescendantOf(content, aContent)) {
846
0
    bool shouldShowFocusRing = window->ShouldShowFocusRing();
847
0
    window->SetFocusedElement(nullptr);
848
0
849
0
    // if this window is currently focused, clear the global focused
850
0
    // element as well, but don't fire any events.
851
0
    if (window == mFocusedWindow) {
852
0
      mFocusedElement = nullptr;
853
0
    } else {
854
0
      // Check if the node that was focused is an iframe or similar by looking
855
0
      // if it has a subdocument. This would indicate that this focused iframe
856
0
      // and its descendants will be going away. We will need to move the
857
0
      // focus somewhere else, so just clear the focus in the toplevel window
858
0
      // so that no element is focused.
859
0
      nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
860
0
      if (subdoc) {
861
0
        nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell();
862
0
        if (docShell) {
863
0
          nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
864
0
          if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
865
0
            ClearFocus(mActiveWindow);
866
0
          }
867
0
        }
868
0
      }
869
0
    }
870
0
871
0
    // Notify the editor in case we removed its ancestor limiter.
872
0
    if (content->IsEditable()) {
873
0
      nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell();
874
0
      if (docShell) {
875
0
        RefPtr<HTMLEditor> htmlEditor = docShell->GetHTMLEditor();
876
0
        if (htmlEditor) {
877
0
          RefPtr<Selection> selection = htmlEditor->GetSelection();
878
0
          if (selection && selection->GetFrameSelection() &&
879
0
              content == selection->GetFrameSelection()->GetAncestorLimiter()) {
880
0
            htmlEditor->FinalizeSelection();
881
0
          }
882
0
        }
883
0
      }
884
0
    }
885
0
886
0
    NotifyFocusStateChange(content, nullptr, shouldShowFocusRing, false);
887
0
  }
888
0
889
0
  return NS_OK;
890
0
}
891
892
NS_IMETHODIMP
893
nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus)
894
0
{
895
0
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
896
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
897
0
898
0
  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
899
0
    LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
900
0
    nsIDocument* doc = window->GetExtantDoc();
901
0
    if (doc && doc->GetDocumentURI()) {
902
0
      LOGFOCUS(("Shown Window: %s",
903
0
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
904
0
    }
905
0
906
0
    if (mFocusedWindow) {
907
0
      doc = mFocusedWindow->GetExtantDoc();
908
0
      if (doc && doc->GetDocumentURI()) {
909
0
        LOGFOCUS((" Focused Window: %s",
910
0
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
911
0
      }
912
0
    }
913
0
  }
914
0
915
0
  if (nsIDocShell* docShell = window->GetDocShell()) {
916
0
    if (nsCOMPtr<nsITabChild> child = docShell->GetTabChild()) {
917
0
      bool active = static_cast<TabChild*>(child.get())->ParentIsActive();
918
0
      ActivateOrDeactivate(window, active);
919
0
    }
920
0
  }
921
0
922
0
  if (mFocusedWindow != window)
923
0
    return NS_OK;
924
0
925
0
  if (aNeedsFocus) {
926
0
    nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
927
0
    RefPtr<Element> currentFocus =
928
0
      GetFocusedDescendant(window, eIncludeAllDescendants,
929
0
                           getter_AddRefs(currentWindow));
930
0
    if (currentWindow)
931
0
      Focus(currentWindow, currentFocus, 0, true, false, false, true);
932
0
  }
933
0
  else {
934
0
    // Sometimes, an element in a window can be focused before the window is
935
0
    // visible, which would mean that the widget may not be properly focused.
936
0
    // When the window becomes visible, make sure the right widget is focused.
937
0
    EnsureCurrentWidgetFocused();
938
0
  }
939
0
940
0
  return NS_OK;
941
0
}
942
943
NS_IMETHODIMP
944
nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow)
945
0
{
946
0
  // if there is no window or it is not the same or an ancestor of the
947
0
  // currently focused window, just return, as the current focus will not
948
0
  // be affected.
949
0
950
0
  NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
951
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
952
0
953
0
  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
954
0
    LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
955
0
    nsAutoCString spec;
956
0
    nsIDocument* doc = window->GetExtantDoc();
957
0
    if (doc && doc->GetDocumentURI()) {
958
0
      LOGFOCUS(("  Hide Window: %s",
959
0
                doc->GetDocumentURI()->GetSpecOrDefault().get()));
960
0
    }
961
0
962
0
    if (mFocusedWindow) {
963
0
      doc = mFocusedWindow->GetExtantDoc();
964
0
      if (doc && doc->GetDocumentURI()) {
965
0
        LOGFOCUS(("  Focused Window: %s",
966
0
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
967
0
      }
968
0
    }
969
0
970
0
    if (mActiveWindow) {
971
0
      doc = mActiveWindow->GetExtantDoc();
972
0
      if (doc && doc->GetDocumentURI()) {
973
0
        LOGFOCUS(("  Active Window: %s",
974
0
                  doc->GetDocumentURI()->GetSpecOrDefault().get()));
975
0
      }
976
0
    }
977
0
  }
978
0
979
0
  if (!IsSameOrAncestor(window, mFocusedWindow))
980
0
    return NS_OK;
981
0
982
0
  // at this point, we know that the window being hidden is either the focused
983
0
  // window, or an ancestor of the focused window. Either way, the focus is no
984
0
  // longer valid, so it needs to be updated.
985
0
986
0
  RefPtr<Element> oldFocusedElement = mFocusedElement.forget();
987
0
988
0
  nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
989
0
  nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
990
0
991
0
  if (oldFocusedElement && oldFocusedElement->IsInComposedDoc()) {
992
0
    NotifyFocusStateChange(oldFocusedElement,
993
0
                           nullptr,
994
0
                           mFocusedWindow->ShouldShowFocusRing(),
995
0
                           false);
996
0
    window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
997
0
998
0
    if (presShell) {
999
0
      SendFocusOrBlurEvent(eBlur, presShell,
1000
0
                           oldFocusedElement->GetComposedDoc(),
1001
0
                           oldFocusedElement, 1, false);
1002
0
    }
1003
0
  }
1004
0
1005
0
  nsPresContext* focusedPresContext =
1006
0
    presShell ? presShell->GetPresContext() : nullptr;
1007
0
  IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1008
0
                                 GetFocusMoveActionCause(0));
1009
0
  if (presShell) {
1010
0
    SetCaretVisible(presShell, false, nullptr);
1011
0
  }
1012
0
1013
0
  // if the docshell being hidden is being destroyed, then we want to move
1014
0
  // focus somewhere else. Call ClearFocus on the toplevel window, which
1015
0
  // will have the effect of clearing the focus and moving the focused window
1016
0
  // to the toplevel window. But if the window isn't being destroyed, we are
1017
0
  // likely just loading a new document in it, so we want to maintain the
1018
0
  // focused window so that the new document gets properly focused.
1019
0
  nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
1020
0
  bool beingDestroyed = !docShellBeingHidden;
1021
0
  if (docShellBeingHidden) {
1022
0
    docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
1023
0
  }
1024
0
  if (beingDestroyed) {
1025
0
    // There is usually no need to do anything if a toplevel window is going
1026
0
    // away, as we assume that WindowLowered will be called. However, this may
1027
0
    // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
1028
0
    // a leak. So if the active window is being destroyed, call WindowLowered
1029
0
    // directly.
1030
0
    if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
1031
0
      WindowLowered(mActiveWindow);
1032
0
    else
1033
0
      ClearFocus(mActiveWindow);
1034
0
    return NS_OK;
1035
0
  }
1036
0
1037
0
  // if the window being hidden is an ancestor of the focused window, adjust
1038
0
  // the focused window so that it points to the one being hidden. This
1039
0
  // ensures that the focused window isn't in a chain of frames that doesn't
1040
0
  // exist any more.
1041
0
  if (window != mFocusedWindow) {
1042
0
    nsCOMPtr<nsIDocShellTreeItem> dsti =
1043
0
      mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
1044
0
    if (dsti) {
1045
0
      nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1046
0
      dsti->GetParent(getter_AddRefs(parentDsti));
1047
0
      if (parentDsti) {
1048
0
        if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parentDsti->GetWindow())
1049
0
          parentWindow->SetFocusedElement(nullptr);
1050
0
      }
1051
0
    }
1052
0
1053
0
    SetFocusedWindowInternal(window);
1054
0
  }
1055
0
1056
0
  return NS_OK;
1057
0
}
1058
1059
NS_IMETHODIMP
1060
nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
1061
0
{
1062
0
  NS_ENSURE_ARG(aDocument);
1063
0
1064
0
  // fire any delayed focus and blur events in the same order that they were added
1065
0
  for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
1066
0
    if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
1067
0
      if (!aDocument->GetInnerWindow() ||
1068
0
          !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
1069
0
        // If the document was navigated away from or is defunct, don't bother
1070
0
        // firing events on it. Note the symmetry between this condition and
1071
0
        // the similar one in nsDocument.cpp:FireOrClearDelayedEvents.
1072
0
        mDelayedBlurFocusEvents.RemoveElementAt(i);
1073
0
        --i;
1074
0
      } else if (!aDocument->EventHandlingSuppressed()) {
1075
0
        EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
1076
0
        nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
1077
0
        nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
1078
0
        nsCOMPtr<EventTarget> relatedTarget = mDelayedBlurFocusEvents[i].mRelatedTarget;
1079
0
        mDelayedBlurFocusEvents.RemoveElementAt(i);
1080
0
1081
0
        FireFocusOrBlurEvent(message, presShell, target, false, false,
1082
0
                             relatedTarget);
1083
0
        --i;
1084
0
      }
1085
0
    }
1086
0
  }
1087
0
1088
0
  return NS_OK;
1089
0
}
1090
1091
NS_IMETHODIMP
1092
nsFocusManager::FocusPlugin(Element* aPlugin)
1093
0
{
1094
0
  NS_ENSURE_ARG(aPlugin);
1095
0
  SetFocusInner(aPlugin, 0, true, false);
1096
0
  return NS_OK;
1097
0
}
1098
1099
NS_IMETHODIMP
1100
nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive)
1101
0
{
1102
0
  nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
1103
0
  NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
1104
0
1105
0
  ActivateOrDeactivate(window, aActive);
1106
0
  return NS_OK;
1107
0
}
1108
1109
/* static */
1110
void
1111
nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
1112
                                       nsIContent* aContentToFocus,
1113
                                       bool aWindowShouldShowFocusRing,
1114
                                       bool aGettingFocus)
1115
0
{
1116
0
  MOZ_ASSERT_IF(aContentToFocus, !aGettingFocus);
1117
0
  if (!aContent->IsElement()) {
1118
0
    return;
1119
0
  }
1120
0
1121
0
  nsIContent* commonAncestor = nullptr;
1122
0
  if (aContentToFocus && aContentToFocus->IsElement()) {
1123
0
    commonAncestor =
1124
0
      nsContentUtils::GetCommonFlattenedTreeAncestor(aContent, aContentToFocus);
1125
0
  }
1126
0
1127
0
  EventStates eventState = NS_EVENT_STATE_FOCUS;
1128
0
  if (aWindowShouldShowFocusRing) {
1129
0
    eventState |= NS_EVENT_STATE_FOCUSRING;
1130
0
  }
1131
0
1132
0
  if (aGettingFocus) {
1133
0
    aContent->AsElement()->AddStates(eventState);
1134
0
  } else {
1135
0
    aContent->AsElement()->RemoveStates(eventState);
1136
0
  }
1137
0
1138
0
  for (nsIContent* content = aContent;
1139
0
       content && content != commonAncestor;
1140
0
       content = content->GetFlattenedTreeParent()) {
1141
0
    if (!content->IsElement()) {
1142
0
      continue;
1143
0
    }
1144
0
1145
0
    Element* element = content->AsElement();
1146
0
    if (aGettingFocus) {
1147
0
      if (element->State().HasState(NS_EVENT_STATE_FOCUS_WITHIN)) {
1148
0
        break;
1149
0
      }
1150
0
      element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN);
1151
0
    } else {
1152
0
      element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN);
1153
0
    }
1154
0
  }
1155
0
}
1156
1157
// static
1158
void
1159
nsFocusManager::EnsureCurrentWidgetFocused()
1160
0
{
1161
0
  if (!mFocusedWindow || sTestMode)
1162
0
    return;
1163
0
1164
0
  // get the main child widget for the focused window and ensure that the
1165
0
  // platform knows that this widget is focused.
1166
0
  nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
1167
0
  if (docShell) {
1168
0
    nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1169
0
    if (presShell) {
1170
0
      nsViewManager* vm = presShell->GetViewManager();
1171
0
      if (vm) {
1172
0
        nsCOMPtr<nsIWidget> widget;
1173
0
        vm->GetRootWidget(getter_AddRefs(widget));
1174
0
        if (widget)
1175
0
          widget->SetFocus(false);
1176
0
      }
1177
0
    }
1178
0
  }
1179
0
}
1180
1181
bool
1182
ActivateOrDeactivateChild(TabParent* aParent, void* aArg)
1183
0
{
1184
0
  bool active = static_cast<bool>(aArg);
1185
0
  Unused << aParent->SendParentActivated(active);
1186
0
  return false;
1187
0
}
1188
1189
void
1190
nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive)
1191
0
{
1192
0
  if (!aWindow) {
1193
0
    return;
1194
0
  }
1195
0
1196
0
  // Inform the DOM window that it has activated or deactivated, so that
1197
0
  // the active attribute is updated on the window.
1198
0
  aWindow->ActivateOrDeactivate(aActive);
1199
0
1200
0
  // Send the activate event.
1201
0
  if (aWindow->GetExtantDoc()) {
1202
0
    nsContentUtils::DispatchEventOnlyToChrome(aWindow->GetExtantDoc(),
1203
0
                                              aWindow->GetCurrentInnerWindow(),
1204
0
                                              aActive ?
1205
0
                                                NS_LITERAL_STRING("activate") :
1206
0
                                                NS_LITERAL_STRING("deactivate"),
1207
0
                                              CanBubble::eYes,
1208
0
                                              Cancelable::eYes,
1209
0
                                              nullptr);
1210
0
  }
1211
0
1212
0
  // Look for any remote child frames, iterate over them and send the activation notification.
1213
0
  nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild,
1214
0
                                          (void *)aActive);
1215
0
}
1216
1217
void
1218
nsFocusManager::SetFocusInner(Element* aNewContent, int32_t aFlags,
1219
                              bool aFocusChanged, bool aAdjustWidget)
1220
0
{
1221
0
  // if the element is not focusable, just return and leave the focus as is
1222
0
  RefPtr<Element> elementToFocus = CheckIfFocusable(aNewContent, aFlags);
1223
0
  if (!elementToFocus) {
1224
0
    return;
1225
0
  }
1226
0
1227
0
  // check if the element to focus is a frame (iframe) containing a child
1228
0
  // document. Frames are never directly focused; instead focusing a frame
1229
0
  // means focus what is inside the frame. To do this, the descendant content
1230
0
  // within the frame is retrieved and that will be focused instead.
1231
0
  nsCOMPtr<nsPIDOMWindowOuter> newWindow;
1232
0
  nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus);
1233
0
  if (subWindow) {
1234
0
    elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
1235
0
                                          getter_AddRefs(newWindow));
1236
0
    // since a window is being refocused, clear aFocusChanged so that the
1237
0
    // caret position isn't updated.
1238
0
    aFocusChanged = false;
1239
0
  }
1240
0
1241
0
  // unless it was set above, retrieve the window for the element to focus
1242
0
  if (!newWindow) {
1243
0
    newWindow = GetCurrentWindow(elementToFocus);
1244
0
  }
1245
0
1246
0
  // if the element is already focused, just return. Note that this happens
1247
0
  // after the frame check above so that we compare the element that will be
1248
0
  // focused rather than the frame it is in.
1249
0
  if (!newWindow ||
1250
0
      (newWindow == mFocusedWindow && elementToFocus == mFocusedElement)) {
1251
0
    return;
1252
0
  }
1253
0
1254
0
  // don't allow focus to be placed in docshells or descendants of docshells
1255
0
  // that are being destroyed. Also, ensure that the page hasn't been
1256
0
  // unloaded. The prevents content from being refocused during an unload event.
1257
0
  nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
1258
0
  nsCOMPtr<nsIDocShell> docShell = newDocShell;
1259
0
  while (docShell) {
1260
0
    bool inUnload;
1261
0
    docShell->GetIsInUnload(&inUnload);
1262
0
    if (inUnload)
1263
0
      return;
1264
0
1265
0
    bool beingDestroyed;
1266
0
    docShell->IsBeingDestroyed(&beingDestroyed);
1267
0
    if (beingDestroyed)
1268
0
      return;
1269
0
1270
0
    nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1271
0
    docShell->GetParent(getter_AddRefs(parentDsti));
1272
0
    docShell = do_QueryInterface(parentDsti);
1273
0
  }
1274
0
1275
0
  // if the new element is in the same window as the currently focused element
1276
0
  bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
1277
0
1278
0
  if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
1279
0
      nsContentUtils::IsHandlingKeyBoardEvent()) {
1280
0
    nsCOMPtr<nsIScriptObjectPrincipal> focused =
1281
0
      do_QueryInterface(mFocusedWindow);
1282
0
    nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
1283
0
      do_QueryInterface(newWindow);
1284
0
    nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
1285
0
    nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
1286
0
    if (!focusedPrincipal || !newPrincipal) {
1287
0
      return;
1288
0
    }
1289
0
    bool subsumes = false;
1290
0
    focusedPrincipal->Subsumes(newPrincipal, &subsumes);
1291
0
    if (!subsumes && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1292
0
      NS_WARNING("Not allowed to focus the new window!");
1293
0
      return;
1294
0
    }
1295
0
  }
1296
0
1297
0
  // to check if the new element is in the active window, compare the
1298
0
  // new root docshell for the new element with the active window's docshell.
1299
0
  bool isElementInActiveWindow = false;
1300
0
1301
0
  nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
1302
0
  nsCOMPtr<nsPIDOMWindowOuter> newRootWindow;
1303
0
  if (dsti) {
1304
0
    nsCOMPtr<nsIDocShellTreeItem> root;
1305
0
    dsti->GetRootTreeItem(getter_AddRefs(root));
1306
0
    newRootWindow = root ? root->GetWindow() : nullptr;
1307
0
1308
0
    isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
1309
0
  }
1310
0
1311
0
  // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
1312
0
  // system. We don't control event dispatch to windowed plugins on non-MacOSX,
1313
0
  // so we can't display the "Press ESC to leave fullscreen mode" warning on
1314
0
  // key input if a windowed plugin is focused, so just exit fullscreen
1315
0
  // to guard against phishing.
1316
0
#ifndef XP_MACOSX
1317
0
  if (elementToFocus &&
1318
0
      nsContentUtils::
1319
0
        GetRootDocument(elementToFocus->OwnerDoc())->GetFullscreenElement() &&
1320
0
      nsContentUtils::HasPluginWithUncontrolledEventDispatch(elementToFocus)) {
1321
0
    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
1322
0
                                    NS_LITERAL_CSTRING("DOM"),
1323
0
                                    elementToFocus->OwnerDoc(),
1324
0
                                    nsContentUtils::eDOM_PROPERTIES,
1325
0
                                    "FocusedWindowedPluginWhileFullscreen");
1326
0
    nsIDocument::AsyncExitFullscreen(elementToFocus->OwnerDoc());
1327
0
  }
1328
0
#endif
1329
0
1330
0
  // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
1331
0
  // shifted away from the current element if the new shell to focus is
1332
0
  // the same or an ancestor shell of the currently focused shell.
1333
0
  bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
1334
0
                            IsSameOrAncestor(newWindow, mFocusedWindow);
1335
0
1336
0
  // if the element is in the active window, frame switching is allowed and
1337
0
  // the content is in a visible window, fire blur and focus events.
1338
0
  bool sendFocusEvent =
1339
0
    isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
1340
0
1341
0
  // When the following conditions are true:
1342
0
  //  * an element has focus
1343
0
  //  * isn't called by trusted event (i.e., called by untrusted event or by js)
1344
0
  //  * the focus is moved to another document's element
1345
0
  // we need to check the permission.
1346
0
  if (sendFocusEvent && mFocusedElement && !nsContentUtils::LegacyIsCallerNativeCode() &&
1347
0
      mFocusedElement->OwnerDoc() != aNewContent->OwnerDoc()) {
1348
0
    // If the caller cannot access the current focused node, the caller should
1349
0
    // not be able to steal focus from it. E.g., When the current focused node
1350
0
    // is in chrome, any web contents should not be able to steal the focus.
1351
0
    sendFocusEvent = nsContentUtils::CanCallerAccess(mFocusedElement);
1352
0
    if (!sendFocusEvent && mMouseButtonEventHandlingDocument) {
1353
0
      // However, while mouse button event is handling, the handling document's
1354
0
      // script should be able to steal focus.
1355
0
      sendFocusEvent =
1356
0
        nsContentUtils::CanCallerAccess(mMouseButtonEventHandlingDocument);
1357
0
    }
1358
0
  }
1359
0
1360
0
  LOGCONTENT("Shift Focus: %s", elementToFocus.get());
1361
0
  LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
1362
0
           aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedElement.get()));
1363
0
  LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
1364
0
           isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
1365
0
1366
0
  if (sendFocusEvent) {
1367
0
    RefPtr<Element> oldFocusedElement = mFocusedElement;
1368
0
    // return if blurring fails or the focus changes during the blur
1369
0
    if (mFocusedWindow) {
1370
0
      // if the focus is being moved to another element in the same document,
1371
0
      // or to a descendant, pass the existing window to Blur so that the
1372
0
      // current node in the existing window is cleared. If moving to a
1373
0
      // window elsewhere, we want to maintain the current node in the
1374
0
      // window but still blur it.
1375
0
      bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
1376
0
      // find the common ancestor of the currently focused window and the new
1377
0
      // window. The ancestor will need to have its currently focused node
1378
0
      // cleared once the document has been blurred. Otherwise, we'll be in a
1379
0
      // state where a document is blurred yet the chain of windows above it
1380
0
      // still points to that document.
1381
0
      // For instance, in the following frame tree:
1382
0
      //   A
1383
0
      //  B C
1384
0
      //  D
1385
0
      // D is focused and we want to focus C. Once D has been blurred, we need
1386
0
      // to clear out the focus in A, otherwise A would still maintain that B
1387
0
      // was focused, and B that D was focused.
1388
0
      nsCOMPtr<nsPIDOMWindowOuter> commonAncestor;
1389
0
      if (!isElementInFocusedWindow)
1390
0
        commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
1391
0
1392
0
      if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
1393
0
                commonAncestor, !isElementInFocusedWindow, aAdjustWidget,
1394
0
                elementToFocus)) {
1395
0
        return;
1396
0
      }
1397
0
    }
1398
0
1399
0
    Focus(newWindow, elementToFocus, aFlags, !isElementInFocusedWindow,
1400
0
          aFocusChanged, false, aAdjustWidget, oldFocusedElement);
1401
0
  }
1402
0
  else {
1403
0
    // otherwise, for inactive windows and when the caller cannot steal the
1404
0
    // focus, update the node in the window, and  raise the window if desired.
1405
0
    if (allowFrameSwitch)
1406
0
      AdjustWindowFocus(newWindow, true);
1407
0
1408
0
    // set the focus node and method as needed
1409
0
    uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1410
0
                           newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1411
0
    newWindow->SetFocusedElement(elementToFocus, focusMethod);
1412
0
    if (aFocusChanged) {
1413
0
      nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
1414
0
1415
0
      nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1416
0
      if (presShell && presShell->DidInitialize())
1417
0
        ScrollIntoView(presShell, elementToFocus, aFlags);
1418
0
    }
1419
0
1420
0
    // update the commands even when inactive so that the attributes for that
1421
0
    // window are up to date.
1422
0
    if (allowFrameSwitch)
1423
0
      newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1424
0
1425
0
    if (aFlags & FLAG_RAISE)
1426
0
      RaiseWindow(newRootWindow);
1427
0
  }
1428
0
}
1429
1430
bool
1431
nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
1432
                                 nsPIDOMWindowOuter* aWindow)
1433
0
{
1434
0
  if (!aWindow || !aPossibleAncestor) {
1435
0
    return false;
1436
0
  }
1437
0
1438
0
  nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell();
1439
0
  nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell();
1440
0
  while (dsti) {
1441
0
    if (dsti == ancestordsti)
1442
0
      return true;
1443
0
    nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1444
0
    dsti->GetParent(getter_AddRefs(parentDsti));
1445
0
    dsti.swap(parentDsti);
1446
0
  }
1447
0
1448
0
  return false;
1449
0
}
1450
1451
already_AddRefed<nsPIDOMWindowOuter>
1452
nsFocusManager::GetCommonAncestor(nsPIDOMWindowOuter* aWindow1,
1453
                                  nsPIDOMWindowOuter* aWindow2)
1454
0
{
1455
0
  NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr);
1456
0
1457
0
  nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell();
1458
0
  NS_ENSURE_TRUE(dsti1, nullptr);
1459
0
1460
0
  nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell();
1461
0
  NS_ENSURE_TRUE(dsti2, nullptr);
1462
0
1463
0
  AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
1464
0
  do {
1465
0
    parents1.AppendElement(dsti1);
1466
0
    nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
1467
0
    dsti1->GetParent(getter_AddRefs(parentDsti1));
1468
0
    dsti1.swap(parentDsti1);
1469
0
  } while (dsti1);
1470
0
  do {
1471
0
    parents2.AppendElement(dsti2);
1472
0
    nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
1473
0
    dsti2->GetParent(getter_AddRefs(parentDsti2));
1474
0
    dsti2.swap(parentDsti2);
1475
0
  } while (dsti2);
1476
0
1477
0
  uint32_t pos1 = parents1.Length();
1478
0
  uint32_t pos2 = parents2.Length();
1479
0
  nsIDocShellTreeItem* parent = nullptr;
1480
0
  uint32_t len;
1481
0
  for (len = std::min(pos1, pos2); len > 0; --len) {
1482
0
    nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
1483
0
    nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
1484
0
    if (child1 != child2) {
1485
0
      break;
1486
0
    }
1487
0
    parent = child1;
1488
0
  }
1489
0
1490
0
  nsCOMPtr<nsPIDOMWindowOuter> window = parent ? parent->GetWindow() : nullptr;
1491
0
  return window.forget();
1492
0
}
1493
1494
void
1495
nsFocusManager::AdjustWindowFocus(nsPIDOMWindowOuter* aWindow,
1496
                                  bool aCheckPermission)
1497
0
{
1498
0
  bool isVisible = IsWindowVisible(aWindow);
1499
0
1500
0
  nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
1501
0
  while (window) {
1502
0
    // get the containing <iframe> or equivalent element so that it can be
1503
0
    // focused below.
1504
0
    nsCOMPtr<Element> frameElement = window->GetFrameElementInternal();
1505
0
1506
0
    nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell();
1507
0
    if (!dsti)
1508
0
      return;
1509
0
    nsCOMPtr<nsIDocShellTreeItem> parentDsti;
1510
0
    dsti->GetParent(getter_AddRefs(parentDsti));
1511
0
    if (!parentDsti) {
1512
0
      return;
1513
0
    }
1514
0
1515
0
    window = parentDsti->GetWindow();
1516
0
    if (window) {
1517
0
      // if the parent window is visible but aWindow was not, then we have
1518
0
      // likely moved up and out from a hidden tab to the browser window, or a
1519
0
      // similar such arrangement. Stop adjusting the current nodes.
1520
0
      if (IsWindowVisible(window) != isVisible)
1521
0
        break;
1522
0
1523
0
      // When aCheckPermission is true, we should check whether the caller can
1524
0
      // access the window or not.  If it cannot access, we should stop the
1525
0
      // adjusting.
1526
0
      if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() &&
1527
0
          !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) {
1528
0
        break;
1529
0
      }
1530
0
1531
0
      window->SetFocusedElement(frameElement);
1532
0
    }
1533
0
  }
1534
0
}
1535
1536
bool
1537
nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow)
1538
0
{
1539
0
  if (!aWindow || aWindow->IsFrozen())
1540
0
    return false;
1541
0
1542
0
  // Check if the inner window is frozen as well. This can happen when a focus change
1543
0
  // occurs while restoring a previous page.
1544
0
  nsPIDOMWindowInner* innerWindow = aWindow->GetCurrentInnerWindow();
1545
0
  if (!innerWindow || innerWindow->IsFrozen())
1546
0
    return false;
1547
0
1548
0
  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1549
0
  nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
1550
0
  if (!baseWin)
1551
0
    return false;
1552
0
1553
0
  bool visible = false;
1554
0
  baseWin->GetVisibility(&visible);
1555
0
  return visible;
1556
0
}
1557
1558
bool
1559
nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
1560
0
{
1561
0
  MOZ_ASSERT(aContent, "aContent must not be NULL");
1562
0
  MOZ_ASSERT(aContent->IsInComposedDoc(), "aContent must be in a document");
1563
0
1564
0
  // If aContent is in designMode, the root element is not focusable.
1565
0
  // NOTE: in designMode, most elements are not focusable, just the document is
1566
0
  //       focusable.
1567
0
  // Also, if aContent is not editable but it isn't in designMode, it's not
1568
0
  // focusable.
1569
0
  // And in userfocusignored context nothing is focusable.
1570
0
  nsIDocument* doc = aContent->GetComposedDoc();
1571
0
  NS_ASSERTION(doc, "aContent must have current document");
1572
0
  return aContent == doc->GetRootElement() &&
1573
0
           (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
1574
0
            nsContentUtils::IsUserFocusIgnored(aContent));
1575
0
}
1576
1577
Element*
1578
nsFocusManager::CheckIfFocusable(Element* aElement, uint32_t aFlags)
1579
0
{
1580
0
  if (!aElement)
1581
0
    return nullptr;
1582
0
1583
0
  // this is a special case for some XUL elements or input number, where an
1584
0
  // anonymous child is actually focusable and not the element itself.
1585
0
  RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement);
1586
0
  if (redirectedFocus) {
1587
0
    return CheckIfFocusable(redirectedFocus, aFlags);
1588
0
  }
1589
0
1590
0
  nsCOMPtr<nsIDocument> doc = aElement->GetComposedDoc();
1591
0
  // can't focus elements that are not in documents
1592
0
  if (!doc) {
1593
0
    LOGCONTENT("Cannot focus %s because content not in document", aElement)
1594
0
    return nullptr;
1595
0
  }
1596
0
1597
0
  // Make sure that our frames are up to date while ensuring the presshell is
1598
0
  // also initialized in case we come from a script calling focus() early.
1599
0
  mEventHandlingNeedsFlush = false;
1600
0
  doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
1601
0
1602
0
  nsIPresShell *shell = doc->GetShell();
1603
0
  if (!shell)
1604
0
    return nullptr;
1605
0
1606
0
  // the root content can always be focused,
1607
0
  // except in userfocusignored context.
1608
0
  if (aElement == doc->GetRootElement()) {
1609
0
    return nsContentUtils::IsUserFocusIgnored(aElement) ? nullptr : aElement;
1610
0
  }
1611
0
1612
0
  // cannot focus content in print preview mode. Only the root can be focused.
1613
0
  nsPresContext* presContext = shell->GetPresContext();
1614
0
  if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
1615
0
    LOGCONTENT("Cannot focus %s while in print preview", aElement)
1616
0
    return nullptr;
1617
0
  }
1618
0
1619
0
  nsIFrame* frame = aElement->GetPrimaryFrame();
1620
0
  if (!frame) {
1621
0
    LOGCONTENT("Cannot focus %s as it has no frame", aElement)
1622
0
    return nullptr;
1623
0
  }
1624
0
1625
0
  if (aElement->IsHTMLElement(nsGkAtoms::area)) {
1626
0
    // HTML areas do not have their own frame, and the img frame we get from
1627
0
    // GetPrimaryFrame() is not relevant as to whether it is focusable or
1628
0
    // not, so we have to do all the relevant checks manually for them.
1629
0
    return frame->IsVisibleConsideringAncestors() &&
1630
0
           aElement->IsFocusable() ? aElement : nullptr;
1631
0
  }
1632
0
1633
0
  // if this is a child frame content node, check if it is visible and
1634
0
  // call the content node's IsFocusable method instead of the frame's
1635
0
  // IsFocusable method. This skips checking the style system and ensures that
1636
0
  // offscreen browsers can still be focused.
1637
0
  nsIDocument* subdoc = doc->GetSubDocumentFor(aElement);
1638
0
  if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
1639
0
    const nsStyleUI* ui = frame->StyleUI();
1640
0
    int32_t tabIndex = (ui->mUserFocus == StyleUserFocus::Ignore ||
1641
0
                        ui->mUserFocus == StyleUserFocus::None) ? -1 : 0;
1642
0
    return aElement->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aElement : nullptr;
1643
0
  }
1644
0
1645
0
  return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aElement : nullptr;
1646
0
}
1647
1648
bool
1649
nsFocusManager::Blur(nsPIDOMWindowOuter* aWindowToClear,
1650
                     nsPIDOMWindowOuter* aAncestorWindowToFocus,
1651
                     bool aIsLeavingDocument,
1652
                     bool aAdjustWidgets,
1653
                     nsIContent* aContentToFocus)
1654
0
{
1655
0
  LOGFOCUS(("<<Blur begin>>"));
1656
0
1657
0
  // hold a reference to the focused content, which may be null
1658
0
  RefPtr<Element> element = mFocusedElement;
1659
0
  if (element) {
1660
0
    if (!element->IsInComposedDoc()) {
1661
0
      mFocusedElement = nullptr;
1662
0
      return true;
1663
0
    }
1664
0
    if (element == mFirstBlurEvent)
1665
0
      return true;
1666
0
  }
1667
0
1668
0
  // hold a reference to the focused window
1669
0
  nsCOMPtr<nsPIDOMWindowOuter> window = mFocusedWindow;
1670
0
  if (!window) {
1671
0
    mFocusedElement = nullptr;
1672
0
    return true;
1673
0
  }
1674
0
1675
0
  nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
1676
0
  if (!docShell) {
1677
0
    mFocusedElement = nullptr;
1678
0
    return true;
1679
0
  }
1680
0
1681
0
  // Keep a ref to presShell since dispatching the DOM event may cause
1682
0
  // the document to be destroyed.
1683
0
  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1684
0
  if (!presShell) {
1685
0
    mFocusedElement = nullptr;
1686
0
    return true;
1687
0
  }
1688
0
1689
0
  bool clearFirstBlurEvent = false;
1690
0
  if (!mFirstBlurEvent) {
1691
0
    mFirstBlurEvent = element;
1692
0
    clearFirstBlurEvent = true;
1693
0
  }
1694
0
1695
0
  nsPresContext* focusedPresContext =
1696
0
    mActiveWindow ? presShell->GetPresContext() : nullptr;
1697
0
  IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
1698
0
                                 GetFocusMoveActionCause(0));
1699
0
1700
0
  // now adjust the actual focus, by clearing the fields in the focus manager
1701
0
  // and in the window.
1702
0
  mFocusedElement = nullptr;
1703
0
  bool shouldShowFocusRing = window->ShouldShowFocusRing();
1704
0
  if (aWindowToClear)
1705
0
    aWindowToClear->SetFocusedElement(nullptr);
1706
0
1707
0
  LOGCONTENT("Element %s has been blurred", element.get());
1708
0
1709
0
  // Don't fire blur event on the root content which isn't editable.
1710
0
  bool sendBlurEvent =
1711
0
    element && element->IsInComposedDoc() && !IsNonFocusableRoot(element);
1712
0
  if (element) {
1713
0
    if (sendBlurEvent) {
1714
0
      NotifyFocusStateChange(element,
1715
0
                             aContentToFocus,
1716
0
                             shouldShowFocusRing,
1717
0
                             false);
1718
0
    }
1719
0
1720
0
    // if an object/plug-in/remote browser is being blurred, move the system focus
1721
0
    // to the parent window, otherwise events will still get fired at the plugin.
1722
0
    // But don't do this if we are blurring due to the window being lowered,
1723
0
    // otherwise, the parent window can get raised again.
1724
0
    if (mActiveWindow) {
1725
0
      nsIFrame* contentFrame = element->GetPrimaryFrame();
1726
0
      nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1727
0
      if (aAdjustWidgets && objectFrame && !sTestMode) {
1728
0
        if (XRE_IsContentProcess()) {
1729
0
          // set focus to the top level window via the chrome process.
1730
0
          nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
1731
0
          if (tabChild) {
1732
0
            static_cast<TabChild*>(tabChild.get())->SendDispatchFocusToTopLevelWindow();
1733
0
          }
1734
0
        } else {
1735
0
          // note that the presshell's widget is being retrieved here, not the one
1736
0
          // for the object frame.
1737
0
          nsViewManager* vm = presShell->GetViewManager();
1738
0
          if (vm) {
1739
0
            nsCOMPtr<nsIWidget> widget;
1740
0
            vm->GetRootWidget(getter_AddRefs(widget));
1741
0
            if (widget) {
1742
0
              // set focus to the top level window but don't raise it.
1743
0
              widget->SetFocus(false);
1744
0
            }
1745
0
          }
1746
0
        }
1747
0
      }
1748
0
    }
1749
0
1750
0
      // if the object being blurred is a remote browser, deactivate remote content
1751
0
    if (TabParent* remote = TabParent::GetFrom(element)) {
1752
0
      remote->Deactivate();
1753
0
      LOGFOCUS(("Remote browser deactivated"));
1754
0
    }
1755
0
  }
1756
0
1757
0
  bool result = true;
1758
0
  if (sendBlurEvent) {
1759
0
    // if there is an active window, update commands. If there isn't an active
1760
0
    // window, then this was a blur caused by the active window being lowered,
1761
0
    // so there is no need to update the commands
1762
0
    if (mActiveWindow)
1763
0
      window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1764
0
1765
0
    SendFocusOrBlurEvent(eBlur, presShell,
1766
0
                         element->GetComposedDoc(), element, 1,
1767
0
                         false, false, aContentToFocus);
1768
0
  }
1769
0
1770
0
  // if we are leaving the document or the window was lowered, make the caret
1771
0
  // invisible.
1772
0
  if (aIsLeavingDocument || !mActiveWindow) {
1773
0
    SetCaretVisible(presShell, false, nullptr);
1774
0
  }
1775
0
1776
0
  RefPtr<AccessibleCaretEventHub> eventHub = presShell->GetAccessibleCaretEventHub();
1777
0
  if (eventHub) {
1778
0
    eventHub->NotifyBlur(aIsLeavingDocument || !mActiveWindow);
1779
0
  }
1780
0
1781
0
  // at this point, it is expected that this window will be still be
1782
0
  // focused, but the focused element will be null, as it was cleared before
1783
0
  // the event. If this isn't the case, then something else was focused during
1784
0
  // the blur event above and we should just return. However, if
1785
0
  // aIsLeavingDocument is set, a new document is desired, so make sure to
1786
0
  // blur the document and window.
1787
0
  if (mFocusedWindow != window ||
1788
0
      (mFocusedElement != nullptr && !aIsLeavingDocument)) {
1789
0
    result = false;
1790
0
  }
1791
0
  else if (aIsLeavingDocument) {
1792
0
    window->TakeFocus(false, 0);
1793
0
1794
0
    // clear the focus so that the ancestor frame hierarchy is in the correct
1795
0
    // state. Pass true because aAncestorWindowToFocus is thought to be
1796
0
    // focused at this point.
1797
0
    if (aAncestorWindowToFocus)
1798
0
      aAncestorWindowToFocus->SetFocusedElement(nullptr, 0, true);
1799
0
1800
0
    SetFocusedWindowInternal(nullptr);
1801
0
    mFocusedElement = nullptr;
1802
0
1803
0
    // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
1804
0
    // that the check is made for suppressed documents. Check to ensure that
1805
0
    // the document isn't null in case someone closed it during the blur above
1806
0
    nsIDocument* doc = window->GetExtantDoc();
1807
0
    if (doc)
1808
0
      SendFocusOrBlurEvent(eBlur, presShell, doc, doc, 1, false);
1809
0
    if (mFocusedWindow == nullptr)
1810
0
      SendFocusOrBlurEvent(eBlur, presShell, doc,
1811
0
                           window->GetCurrentInnerWindow(), 1, false);
1812
0
1813
0
    // check if a different window was focused
1814
0
    result = (mFocusedWindow == nullptr && mActiveWindow);
1815
0
  }
1816
0
  else if (mActiveWindow) {
1817
0
    // Otherwise, the blur of the element without blurring the document
1818
0
    // occurred normally. Call UpdateCaret to redisplay the caret at the right
1819
0
    // location within the document. This is needed to ensure that the caret
1820
0
    // used for caret browsing is made visible again when an input field is
1821
0
    // blurred.
1822
0
    UpdateCaret(false, true, nullptr);
1823
0
  }
1824
0
1825
0
  if (clearFirstBlurEvent)
1826
0
    mFirstBlurEvent = nullptr;
1827
0
1828
0
  return result;
1829
0
}
1830
1831
void
1832
nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow,
1833
                      Element* aElement,
1834
                      uint32_t aFlags,
1835
                      bool aIsNewDocument,
1836
                      bool aFocusChanged,
1837
                      bool aWindowRaised,
1838
                      bool aAdjustWidgets,
1839
                      nsIContent* aContentLostFocus)
1840
0
{
1841
0
  LOGFOCUS(("<<Focus begin>>"));
1842
0
1843
0
  if (!aWindow)
1844
0
    return;
1845
0
1846
0
  if (aElement && (aElement == mFirstFocusEvent || aElement == mFirstBlurEvent))
1847
0
    return;
1848
0
1849
0
  // Keep a reference to the presShell since dispatching the DOM event may
1850
0
  // cause the document to be destroyed.
1851
0
  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
1852
0
  if (!docShell)
1853
0
    return;
1854
0
1855
0
  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
1856
0
  if (!presShell)
1857
0
    return;
1858
0
1859
0
  // If the focus actually changed, set the focus method (mouse, keyboard, etc).
1860
0
  // Otherwise, just get the current focus method and use that. This ensures
1861
0
  // that the method is set during the document and window focus events.
1862
0
  uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
1863
0
                         aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
1864
0
1865
0
  if (!IsWindowVisible(aWindow)) {
1866
0
    // if the window isn't visible, for instance because it is a hidden tab,
1867
0
    // update the current focus and scroll it into view but don't do anything else
1868
0
    if (CheckIfFocusable(aElement, aFlags)) {
1869
0
      aWindow->SetFocusedElement(aElement, focusMethod);
1870
0
      if (aFocusChanged)
1871
0
        ScrollIntoView(presShell, aElement, aFlags);
1872
0
    }
1873
0
    return;
1874
0
  }
1875
0
1876
0
  bool clearFirstFocusEvent = false;
1877
0
  if (!mFirstFocusEvent) {
1878
0
    mFirstFocusEvent = aElement;
1879
0
    clearFirstFocusEvent = true;
1880
0
  }
1881
0
1882
0
  LOGCONTENT("Element %s has been focused", aElement);
1883
0
1884
0
  if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
1885
0
    nsIDocument* docm = aWindow->GetExtantDoc();
1886
0
    if (docm) {
1887
0
      LOGCONTENT(" from %s", docm->GetRootElement());
1888
0
    }
1889
0
    LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
1890
0
             aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
1891
0
  }
1892
0
1893
0
  if (aIsNewDocument) {
1894
0
    // if this is a new document, update the parent chain of frames so that
1895
0
    // focus can be traversed from the top level down to the newly focused
1896
0
    // window.
1897
0
    AdjustWindowFocus(aWindow, false);
1898
0
  }
1899
0
1900
0
  // indicate that the window has taken focus.
1901
0
  if (aWindow->TakeFocus(true, focusMethod))
1902
0
    aIsNewDocument = true;
1903
0
1904
0
  SetFocusedWindowInternal(aWindow);
1905
0
1906
0
  // Update the system focus by focusing the root widget.  But avoid this
1907
0
  // if 1) aAdjustWidgets is false or 2) aElement is a plugin that has its
1908
0
  // own widget and is either already focused or is about to be focused.
1909
0
  nsCOMPtr<nsIWidget> objectFrameWidget;
1910
0
  if (aElement) {
1911
0
    nsIFrame* contentFrame = aElement->GetPrimaryFrame();
1912
0
    nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
1913
0
    if (objectFrame)
1914
0
      objectFrameWidget = objectFrame->GetWidget();
1915
0
  }
1916
0
  if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
1917
0
    nsViewManager* vm = presShell->GetViewManager();
1918
0
    if (vm) {
1919
0
      nsCOMPtr<nsIWidget> widget;
1920
0
      vm->GetRootWidget(getter_AddRefs(widget));
1921
0
      if (widget)
1922
0
        widget->SetFocus(false);
1923
0
    }
1924
0
  }
1925
0
1926
0
  // if switching to a new document, first fire the focus event on the
1927
0
  // document and then the window.
1928
0
  if (aIsNewDocument) {
1929
0
    nsIDocument* doc = aWindow->GetExtantDoc();
1930
0
    // The focus change should be notified to IMEStateManager from here if
1931
0
    // the focused element is a designMode editor since any content won't
1932
0
    // receive focus event.
1933
0
    if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
1934
0
      IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
1935
0
                                     GetFocusMoveActionCause(aFlags));
1936
0
    }
1937
0
    if (doc) {
1938
0
      SendFocusOrBlurEvent(eFocus, presShell, doc,
1939
0
                           doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1940
0
    }
1941
0
    if (mFocusedWindow == aWindow && mFocusedElement == nullptr) {
1942
0
      SendFocusOrBlurEvent(eFocus, presShell, doc,
1943
0
                           aWindow->GetCurrentInnerWindow(),
1944
0
                           aFlags & FOCUSMETHOD_MASK, aWindowRaised);
1945
0
    }
1946
0
  }
1947
0
1948
0
  // check to ensure that the element is still focusable, and that nothing
1949
0
  // else was focused during the events above.
1950
0
  if (CheckIfFocusable(aElement, aFlags) &&
1951
0
      mFocusedWindow == aWindow && mFocusedElement == nullptr) {
1952
0
    mFocusedElement = aElement;
1953
0
1954
0
    nsIContent* focusedNode = aWindow->GetFocusedElement();
1955
0
    bool isRefocus = focusedNode && focusedNode->IsEqualNode(aElement);
1956
0
1957
0
    aWindow->SetFocusedElement(aElement, focusMethod);
1958
0
1959
0
    // if the focused element changed, scroll it into view
1960
0
    if (aElement && aFocusChanged) {
1961
0
      ScrollIntoView(presShell, aElement, aFlags);
1962
0
    }
1963
0
1964
0
    bool sendFocusEvent =
1965
0
      aElement && aElement->IsInComposedDoc() && !IsNonFocusableRoot(aElement);
1966
0
    nsPresContext* presContext = presShell->GetPresContext();
1967
0
    if (sendFocusEvent) {
1968
0
      NotifyFocusStateChange(aElement,
1969
0
                             nullptr,
1970
0
                             aWindow->ShouldShowFocusRing(),
1971
0
                             true);
1972
0
1973
0
      // if this is an object/plug-in/remote browser, focus its widget.  Note that we might
1974
0
      // no longer be in the same document, due to the events we fired above when
1975
0
      // aIsNewDocument.
1976
0
      if (presShell->GetDocument() == aElement->GetComposedDoc()) {
1977
0
        if (aAdjustWidgets && objectFrameWidget && !sTestMode)
1978
0
          objectFrameWidget->SetFocus(false);
1979
0
1980
0
        // if the object being focused is a remote browser, activate remote content
1981
0
        if (TabParent* remote = TabParent::GetFrom(aElement)) {
1982
0
          remote->Activate();
1983
0
          LOGFOCUS(("Remote browser activated"));
1984
0
        }
1985
0
      }
1986
0
1987
0
      IMEStateManager::OnChangeFocus(presContext, aElement,
1988
0
                                     GetFocusMoveActionCause(aFlags));
1989
0
1990
0
      // as long as this focus wasn't because a window was raised, update the
1991
0
      // commands
1992
0
      // XXXndeakin P2 someone could adjust the focus during the update
1993
0
      if (!aWindowRaised)
1994
0
        aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
1995
0
1996
0
      SendFocusOrBlurEvent(eFocus, presShell,
1997
0
                           aElement->GetComposedDoc(),
1998
0
                           aElement, aFlags & FOCUSMETHOD_MASK,
1999
0
                           aWindowRaised, isRefocus, aContentLostFocus);
2000
0
    } else {
2001
0
      IMEStateManager::OnChangeFocus(presContext, nullptr,
2002
0
                                     GetFocusMoveActionCause(aFlags));
2003
0
      if (!aWindowRaised) {
2004
0
        aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
2005
0
      }
2006
0
    }
2007
0
  }
2008
0
  else {
2009
0
    // If the window focus event (fired above when aIsNewDocument) caused
2010
0
    // the plugin not to be focusable, update the system focus by focusing
2011
0
    // the root widget.
2012
0
    if (aAdjustWidgets && objectFrameWidget &&
2013
0
        mFocusedWindow == aWindow && mFocusedElement == nullptr &&
2014
0
        !sTestMode) {
2015
0
      nsViewManager* vm = presShell->GetViewManager();
2016
0
      if (vm) {
2017
0
        nsCOMPtr<nsIWidget> widget;
2018
0
        vm->GetRootWidget(getter_AddRefs(widget));
2019
0
        if (widget)
2020
0
          widget->SetFocus(false);
2021
0
      }
2022
0
    }
2023
0
2024
0
    if (!mFocusedElement) {
2025
0
      // When there is no focused element, IMEStateManager needs to adjust IME
2026
0
      // enabled state with the document.
2027
0
      nsPresContext* presContext = presShell->GetPresContext();
2028
0
      IMEStateManager::OnChangeFocus(presContext, nullptr,
2029
0
                                     GetFocusMoveActionCause(aFlags));
2030
0
    }
2031
0
2032
0
    if (!aWindowRaised)
2033
0
      aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
2034
0
  }
2035
0
2036
0
  // update the caret visibility and position to match the newly focused
2037
0
  // element. However, don't update the position if this was a focus due to a
2038
0
  // mouse click as the selection code would already have moved the caret as
2039
0
  // needed. If this is a different document than was focused before, also
2040
0
  // update the caret's visibility. If this is the same document, the caret
2041
0
  // visibility should be the same as before so there is no need to update it.
2042
0
  if (mFocusedElement == aElement)
2043
0
    UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
2044
0
                mFocusedElement);
2045
0
2046
0
  if (clearFirstFocusEvent)
2047
0
    mFirstFocusEvent = nullptr;
2048
0
}
2049
2050
class FocusBlurEvent : public Runnable
2051
{
2052
public:
2053
  FocusBlurEvent(nsISupports* aTarget,
2054
                 EventMessage aEventMessage,
2055
                 nsPresContext* aContext,
2056
                 bool aWindowRaised,
2057
                 bool aIsRefocus,
2058
                 EventTarget* aRelatedTarget)
2059
    : mozilla::Runnable("FocusBlurEvent")
2060
    , mTarget(aTarget)
2061
    , mContext(aContext)
2062
    , mEventMessage(aEventMessage)
2063
    , mWindowRaised(aWindowRaised)
2064
    , mIsRefocus(aIsRefocus)
2065
    , mRelatedTarget(aRelatedTarget)
2066
0
  {
2067
0
  }
2068
2069
  NS_IMETHOD Run() override
2070
0
  {
2071
0
    InternalFocusEvent event(true, mEventMessage);
2072
0
    event.mFlags.mBubbles = false;
2073
0
    event.mFlags.mCancelable = false;
2074
0
    event.mFromRaise = mWindowRaised;
2075
0
    event.mIsRefocus = mIsRefocus;
2076
0
    event.mRelatedTarget = mRelatedTarget;
2077
0
    return EventDispatcher::Dispatch(mTarget, mContext, &event);
2078
0
  }
2079
2080
  nsCOMPtr<nsISupports>   mTarget;
2081
  RefPtr<nsPresContext> mContext;
2082
  EventMessage            mEventMessage;
2083
  bool                    mWindowRaised;
2084
  bool                    mIsRefocus;
2085
  nsCOMPtr<EventTarget>   mRelatedTarget;
2086
};
2087
2088
class FocusInOutEvent : public Runnable
2089
{
2090
public:
2091
  FocusInOutEvent(nsISupports* aTarget,
2092
                  EventMessage aEventMessage,
2093
                  nsPresContext* aContext,
2094
                  nsPIDOMWindowOuter* aOriginalFocusedWindow,
2095
                  nsIContent* aOriginalFocusedContent,
2096
                  EventTarget* aRelatedTarget)
2097
    : mozilla::Runnable("FocusInOutEvent")
2098
    , mTarget(aTarget)
2099
    , mContext(aContext)
2100
    , mEventMessage(aEventMessage)
2101
    , mOriginalFocusedWindow(aOriginalFocusedWindow)
2102
    , mOriginalFocusedContent(aOriginalFocusedContent)
2103
    , mRelatedTarget(aRelatedTarget)
2104
0
  {
2105
0
  }
2106
2107
  NS_IMETHOD Run() override
2108
0
  {
2109
0
    nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ?
2110
0
        mOriginalFocusedWindow->GetFocusedElement() :
2111
0
        nullptr;
2112
0
    // Blink does not check that focus is the same after blur, but WebKit does.
2113
0
    // Opt to follow Blink's behavior (see bug 687787).
2114
0
    if (mEventMessage == eFocusOut ||
2115
0
        originalWindowFocus == mOriginalFocusedContent) {
2116
0
      InternalFocusEvent event(true, mEventMessage);
2117
0
      event.mFlags.mBubbles = true;
2118
0
      event.mFlags.mCancelable = false;
2119
0
      event.mRelatedTarget = mRelatedTarget;
2120
0
      return EventDispatcher::Dispatch(mTarget, mContext, &event);
2121
0
    }
2122
0
    return NS_OK;
2123
0
  }
2124
2125
  nsCOMPtr<nsISupports>        mTarget;
2126
  RefPtr<nsPresContext>        mContext;
2127
  EventMessage                 mEventMessage;
2128
  nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
2129
  nsCOMPtr<nsIContent>         mOriginalFocusedContent;
2130
  nsCOMPtr<EventTarget>        mRelatedTarget;
2131
};
2132
2133
static nsIDocument*
2134
GetDocumentHelper(EventTarget* aTarget)
2135
0
{
2136
0
  nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
2137
0
  if (!node) {
2138
0
    nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget);
2139
0
    return win ? win->GetExtantDoc() : nullptr;
2140
0
  }
2141
0
2142
0
  return node->OwnerDoc();
2143
0
}
2144
2145
void nsFocusManager::FireFocusInOrOutEvent(EventMessage aEventMessage,
2146
                                     nsIPresShell* aPresShell,
2147
                                     nsISupports* aTarget,
2148
                                     nsPIDOMWindowOuter* aCurrentFocusedWindow,
2149
                                     nsIContent* aCurrentFocusedContent,
2150
                                     EventTarget* aRelatedTarget)
2151
0
{
2152
0
  NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
2153
0
      "Wrong event type for FireFocusInOrOutEvent");
2154
0
2155
0
  nsContentUtils::AddScriptRunner(
2156
0
      new FocusInOutEvent(
2157
0
        aTarget,
2158
0
        aEventMessage,
2159
0
        aPresShell->GetPresContext(),
2160
0
        aCurrentFocusedWindow,
2161
0
        aCurrentFocusedContent,
2162
0
        aRelatedTarget));
2163
0
}
2164
2165
void
2166
nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
2167
                                     nsIPresShell* aPresShell,
2168
                                     nsIDocument* aDocument,
2169
                                     nsISupports* aTarget,
2170
                                     uint32_t aFocusMethod,
2171
                                     bool aWindowRaised,
2172
                                     bool aIsRefocus,
2173
                                     EventTarget* aRelatedTarget)
2174
0
{
2175
0
  NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
2176
0
               "Wrong event type for SendFocusOrBlurEvent");
2177
0
2178
0
  nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
2179
0
  nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget);
2180
0
  nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
2181
0
2182
0
  // set aRelatedTarget to null if it's not in the same document as eventTarget
2183
0
  if (eventTargetDoc != relatedTargetDoc) {
2184
0
    aRelatedTarget = nullptr;
2185
0
  }
2186
0
2187
0
  bool dontDispatchEvent =
2188
0
    eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
2189
0
2190
0
  if (!dontDispatchEvent && aDocument && aDocument->EventHandlingSuppressed()) {
2191
0
    for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
2192
0
      // if this event was already queued, remove it and append it to the end
2193
0
      if (mDelayedBlurFocusEvents[i - 1].mEventMessage == aEventMessage &&
2194
0
          mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
2195
0
          mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
2196
0
          mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget &&
2197
0
          mDelayedBlurFocusEvents[i - 1].mRelatedTarget == aRelatedTarget) {
2198
0
        mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
2199
0
      }
2200
0
    }
2201
0
2202
0
    mDelayedBlurFocusEvents.AppendElement(
2203
0
      nsDelayedBlurOrFocusEvent(aEventMessage, aPresShell,
2204
0
                                aDocument, eventTarget, aRelatedTarget));
2205
0
    return;
2206
0
  }
2207
0
2208
0
  // If mDelayedBlurFocusEvents queue is not empty, check if there are events
2209
0
  // that belongs to this doc, if yes, fire them first.
2210
0
  if (aDocument && !aDocument->EventHandlingSuppressed() &&
2211
0
      mDelayedBlurFocusEvents.Length()) {
2212
0
    FireDelayedEvents(aDocument);
2213
0
  }
2214
0
2215
0
  FireFocusOrBlurEvent(aEventMessage, aPresShell, aTarget, aWindowRaised,
2216
0
                       aIsRefocus, aRelatedTarget);
2217
0
}
2218
2219
void
2220
nsFocusManager::FireFocusOrBlurEvent(EventMessage aEventMessage,
2221
                                     nsIPresShell* aPresShell,
2222
                                     nsISupports* aTarget,
2223
                                     bool aWindowRaised,
2224
                                     bool aIsRefocus,
2225
                                     EventTarget* aRelatedTarget)
2226
0
{
2227
0
  nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
2228
0
  nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget);
2229
0
  nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
2230
0
  nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
2231
0
  nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget);
2232
0
  nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ?
2233
0
      currentWindow->GetFocusedElement() : nullptr;
2234
0
2235
0
  bool dontDispatchEvent =
2236
0
    eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
2237
0
2238
0
#ifdef ACCESSIBILITY
2239
0
  nsAccessibilityService* accService = GetAccService();
2240
0
  if (accService) {
2241
0
    if (aEventMessage == eFocus) {
2242
0
      accService->NotifyOfDOMFocus(aTarget);
2243
0
    } else {
2244
0
      accService->NotifyOfDOMBlur(aTarget);
2245
0
    }
2246
0
  }
2247
0
#endif
2248
0
2249
0
  if (!dontDispatchEvent) {
2250
0
    nsContentUtils::AddScriptRunner(
2251
0
      new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
2252
0
                         aWindowRaised, aIsRefocus, aRelatedTarget));
2253
0
2254
0
    // Check that the target is not a window or document before firing
2255
0
    // focusin/focusout. Other browsers do not fire focusin/focusout on window,
2256
0
    // despite being required in the spec, so follow their behavior.
2257
0
    //
2258
0
    // As for document, we should not even fire focus/blur, but until then, we
2259
0
    // need this check. targetDocument should be removed once bug 1228802 is
2260
0
    // resolved.
2261
0
    if (!targetWindow && !targetDocument) {
2262
0
      EventMessage focusInOrOutMessage = aEventMessage == eFocus ? eFocusIn : eFocusOut;
2263
0
      FireFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
2264
0
          currentWindow, currentFocusedContent, aRelatedTarget);
2265
0
    }
2266
0
  }
2267
0
}
2268
2269
void
2270
nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
2271
                               nsIContent* aContent,
2272
                               uint32_t aFlags)
2273
0
{
2274
0
  // if the noscroll flag isn't set, scroll the newly focused element into view
2275
0
  if (!(aFlags & FLAG_NOSCROLL))
2276
0
    aPresShell->ScrollContentIntoView(aContent,
2277
0
                                      nsIPresShell::ScrollAxis(
2278
0
                                        nsIPresShell::SCROLL_MINIMUM,
2279
0
                                        nsIPresShell::SCROLL_IF_NOT_VISIBLE),
2280
0
                                      nsIPresShell::ScrollAxis(
2281
0
                                        nsIPresShell::SCROLL_MINIMUM,
2282
0
                                        nsIPresShell::SCROLL_IF_NOT_VISIBLE),
2283
0
                                      nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
2284
0
}
2285
2286
2287
void
2288
nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow)
2289
0
{
2290
0
  // don't raise windows that are already raised or are in the process of
2291
0
  // being lowered
2292
0
  if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
2293
0
    return;
2294
0
2295
0
  if (sTestMode) {
2296
0
    // In test mode, emulate the existing window being lowered and the new
2297
0
    // window being raised. This happens in a separate runnable to avoid
2298
0
    // touching multiple windows in the current runnable.
2299
0
    nsCOMPtr<nsPIDOMWindowOuter> active(mActiveWindow);
2300
0
    nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
2301
0
    RefPtr<nsFocusManager> self(this);
2302
0
    NS_DispatchToCurrentThread(NS_NewRunnableFunction(
2303
0
      "nsFocusManager::RaiseWindow", [self, active, window]() -> void {
2304
0
        if (active) {
2305
0
          self->WindowLowered(active);
2306
0
        }
2307
0
        self->WindowRaised(window);
2308
0
      }));
2309
0
    return;
2310
0
  }
2311
0
2312
#if defined(XP_WIN)
2313
  // Windows would rather we focus the child widget, otherwise, the toplevel
2314
  // widget will always end up being focused. Fortunately, focusing the child
2315
  // widget will also have the effect of raising the window this widget is in.
2316
  // But on other platforms, we can just focus the toplevel widget to raise
2317
  // the window.
2318
  nsCOMPtr<nsPIDOMWindowOuter> childWindow;
2319
  GetFocusedDescendant(aWindow, eIncludeAllDescendants,
2320
                       getter_AddRefs(childWindow));
2321
  if (!childWindow)
2322
    childWindow = aWindow;
2323
2324
  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2325
  if (!docShell)
2326
    return;
2327
2328
  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
2329
  if (!presShell)
2330
    return;
2331
2332
  nsViewManager* vm = presShell->GetViewManager();
2333
  if (vm) {
2334
    nsCOMPtr<nsIWidget> widget;
2335
    vm->GetRootWidget(getter_AddRefs(widget));
2336
    if (widget)
2337
      widget->SetFocus(true);
2338
  }
2339
#else
2340
0
  nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
2341
0
    do_QueryInterface(aWindow->GetDocShell());
2342
0
  if (treeOwnerAsWin) {
2343
0
    nsCOMPtr<nsIWidget> widget;
2344
0
    treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
2345
0
    if (widget)
2346
0
      widget->SetFocus(true);
2347
0
  }
2348
0
#endif
2349
0
}
2350
2351
void
2352
nsFocusManager::UpdateCaretForCaretBrowsingMode()
2353
0
{
2354
0
  UpdateCaret(false, true, mFocusedElement);
2355
0
}
2356
2357
void
2358
nsFocusManager::UpdateCaret(bool aMoveCaretToFocus,
2359
                            bool aUpdateVisibility,
2360
                            nsIContent* aContent)
2361
0
{
2362
0
  LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
2363
0
2364
0
  if (!mFocusedWindow)
2365
0
    return;
2366
0
2367
0
  // this is called when a document is focused or when the caretbrowsing
2368
0
  // preference is changed
2369
0
  nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
2370
0
  nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
2371
0
  if (!dsti)
2372
0
    return;
2373
0
2374
0
  if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
2375
0
    return;  // Never browse with caret in chrome
2376
0
  }
2377
0
2378
0
  bool browseWithCaret =
2379
0
    Preferences::GetBool("accessibility.browsewithcaret");
2380
0
2381
0
  nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
2382
0
  if (!presShell)
2383
0
    return;
2384
0
2385
0
  // If this is an editable document which isn't contentEditable, or a
2386
0
  // contentEditable document and the node to focus is contentEditable,
2387
0
  // return, so that we don't mess with caret visibility.
2388
0
  bool isEditable = false;
2389
0
  focusedDocShell->GetEditable(&isEditable);
2390
0
2391
0
  if (isEditable) {
2392
0
    nsCOMPtr<nsIHTMLDocument> doc =
2393
0
      do_QueryInterface(presShell->GetDocument());
2394
0
2395
0
    bool isContentEditableDoc =
2396
0
      doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
2397
0
2398
0
    bool isFocusEditable =
2399
0
      aContent && aContent->HasFlag(NODE_IS_EDITABLE);
2400
0
    if (!isContentEditableDoc || isFocusEditable)
2401
0
      return;
2402
0
  }
2403
0
2404
0
  if (!isEditable && aMoveCaretToFocus)
2405
0
    MoveCaretToFocus(presShell, aContent);
2406
0
2407
0
  if (!aUpdateVisibility)
2408
0
    return;
2409
0
2410
0
  // XXXndeakin this doesn't seem right. It should be checking for this only
2411
0
  // on the nearest ancestor frame which is a chrome frame. But this is
2412
0
  // what the existing code does, so just leave it for now.
2413
0
  if (!browseWithCaret) {
2414
0
    nsCOMPtr<Element> docElement =
2415
0
      mFocusedWindow->GetFrameElementInternal();
2416
0
    if (docElement)
2417
0
      browseWithCaret = docElement->AttrValueIs(kNameSpaceID_None,
2418
0
                                                nsGkAtoms::showcaret,
2419
0
                                                NS_LITERAL_STRING("true"),
2420
0
                                                eCaseMatters);
2421
0
  }
2422
0
2423
0
  SetCaretVisible(presShell, browseWithCaret, aContent);
2424
0
}
2425
2426
void
2427
nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
2428
0
{
2429
0
  nsCOMPtr<nsIDocument> doc = aPresShell->GetDocument();
2430
0
  if (doc) {
2431
0
    RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2432
0
    RefPtr<Selection> domSelection =
2433
0
      frameSelection->GetSelection(SelectionType::eNormal);
2434
0
    if (domSelection) {
2435
0
      // First clear the selection. This way, if there is no currently focused
2436
0
      // content, the selection will just be cleared.
2437
0
      domSelection->RemoveAllRanges(IgnoreErrors());
2438
0
      if (aContent) {
2439
0
        ErrorResult rv;
2440
0
        RefPtr<nsRange> newRange = doc->CreateRange(rv);
2441
0
        if (NS_WARN_IF(rv.Failed())) {
2442
0
          rv.SuppressException();
2443
0
          return;
2444
0
        }
2445
0
2446
0
        // Set the range to the start of the currently focused node
2447
0
        // Make sure it's collapsed
2448
0
        newRange->SelectNodeContents(*aContent, IgnoreErrors());
2449
0
2450
0
        if (!aContent->GetFirstChild() ||
2451
0
            aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
2452
0
          // If current focus node is a leaf, set range to before the
2453
0
          // node by using the parent as a container.
2454
0
          // This prevents it from appearing as selected.
2455
0
          newRange->SetStartBefore(*aContent, IgnoreErrors());
2456
0
          newRange->SetEndBefore(*aContent, IgnoreErrors());
2457
0
        }
2458
0
        domSelection->AddRange(*newRange, IgnoreErrors());
2459
0
        domSelection->CollapseToStart(IgnoreErrors());
2460
0
      }
2461
0
    }
2462
0
  }
2463
0
}
2464
2465
nsresult
2466
nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
2467
                                bool aVisible,
2468
                                nsIContent* aContent)
2469
0
{
2470
0
  // When browsing with caret, make sure caret is visible after new focus
2471
0
  // Return early if there is no caret. This can happen for the testcase
2472
0
  // for bug 308025 where a window is closed in a blur handler.
2473
0
  RefPtr<nsCaret> caret = aPresShell->GetCaret();
2474
0
  if (!caret)
2475
0
    return NS_OK;
2476
0
2477
0
  bool caretVisible = caret->IsVisible();
2478
0
  if (!aVisible && !caretVisible)
2479
0
    return NS_OK;
2480
0
2481
0
  RefPtr<nsFrameSelection> frameSelection;
2482
0
  if (aContent) {
2483
0
    NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
2484
0
                 "Wrong document?");
2485
0
    nsIFrame *focusFrame = aContent->GetPrimaryFrame();
2486
0
    if (focusFrame)
2487
0
      frameSelection = focusFrame->GetFrameSelection();
2488
0
  }
2489
0
2490
0
  RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
2491
0
2492
0
  if (docFrameSelection && caret &&
2493
0
     (frameSelection == docFrameSelection || !aContent)) {
2494
0
    Selection* domSelection =
2495
0
      docFrameSelection->GetSelection(SelectionType::eNormal);
2496
0
    if (domSelection) {
2497
0
      nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
2498
0
      if (!selCon) {
2499
0
        return NS_ERROR_FAILURE;
2500
0
      }
2501
0
      // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
2502
0
      selCon->SetCaretEnabled(false);
2503
0
2504
0
      // Caret must blink on non-editable elements
2505
0
      caret->SetIgnoreUserModify(true);
2506
0
      // Tell the caret which selection to use
2507
0
      caret->SetSelection(domSelection);
2508
0
2509
0
      // In content, we need to set the caret. The only special case is edit
2510
0
      // fields, which have a different frame selection from the document.
2511
0
      // They will take care of making the caret visible themselves.
2512
0
2513
0
      selCon->SetCaretReadOnly(false);
2514
0
      selCon->SetCaretEnabled(aVisible);
2515
0
    }
2516
0
  }
2517
0
2518
0
  return NS_OK;
2519
0
}
2520
2521
nsresult
2522
nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
2523
                                     nsIPresShell* aPresShell,
2524
                                     nsIContent **aStartContent,
2525
                                     nsIContent **aEndContent)
2526
0
{
2527
0
  *aStartContent = *aEndContent = nullptr;
2528
0
  nsPresContext* presContext = aPresShell->GetPresContext();
2529
0
  NS_ASSERTION(presContext, "mPresContent is null!!");
2530
0
2531
0
  RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
2532
0
2533
0
  RefPtr<Selection> domSelection;
2534
0
  if (frameSelection) {
2535
0
    domSelection = frameSelection->GetSelection(SelectionType::eNormal);
2536
0
  }
2537
0
2538
0
  bool isCollapsed = false;
2539
0
  nsCOMPtr<nsIContent> startContent, endContent;
2540
0
  uint32_t startOffset = 0;
2541
0
  if (domSelection) {
2542
0
    isCollapsed = domSelection->IsCollapsed();
2543
0
    RefPtr<nsRange> domRange = domSelection->GetRangeAt(0);
2544
0
    if (domRange) {
2545
0
      nsCOMPtr<nsINode> startNode = domRange->GetStartContainer();
2546
0
      nsCOMPtr<nsINode> endNode = domRange->GetEndContainer();
2547
0
      startOffset = domRange->StartOffset();
2548
0
2549
0
      nsIContent *childContent = nullptr;
2550
0
2551
0
      startContent = do_QueryInterface(startNode);
2552
0
      if (startContent && startContent->IsElement()) {
2553
0
        childContent = startContent->GetChildAt_Deprecated(startOffset);
2554
0
        if (childContent) {
2555
0
          startContent = childContent;
2556
0
        }
2557
0
      }
2558
0
2559
0
      endContent = do_QueryInterface(endNode);
2560
0
      if (endContent && endContent->IsElement()) {
2561
0
        uint32_t endOffset = domRange->EndOffset();
2562
0
        childContent = endContent->GetChildAt_Deprecated(endOffset);
2563
0
        if (childContent) {
2564
0
          endContent = childContent;
2565
0
        }
2566
0
      }
2567
0
    }
2568
0
  }
2569
0
  else {
2570
0
    return NS_ERROR_INVALID_ARG;
2571
0
  }
2572
0
2573
0
  nsIFrame *startFrame = nullptr;
2574
0
  if (startContent) {
2575
0
    startFrame = startContent->GetPrimaryFrame();
2576
0
    if (isCollapsed) {
2577
0
      // Next check to see if our caret is at the very end of a node
2578
0
      // If so, the caret is actually sitting in front of the next
2579
0
      // logical frame's primary node - so for this case we need to
2580
0
      // change caretContent to that node.
2581
0
2582
0
      if (startContent->NodeType() == nsINode::TEXT_NODE) {
2583
0
        nsAutoString nodeValue;
2584
0
        startContent->GetAsText()->AppendTextTo(nodeValue);
2585
0
2586
0
        bool isFormControl =
2587
0
          startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
2588
0
2589
0
        if (nodeValue.Length() == startOffset && !isFormControl &&
2590
0
            startContent != aDocument->GetRootElement()) {
2591
0
          // Yes, indeed we were at the end of the last node
2592
0
          nsCOMPtr<nsIFrameEnumerator> frameTraversal;
2593
0
          nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
2594
0
                                             presContext, startFrame,
2595
0
                                             eLeaf,
2596
0
                                             false, // aVisual
2597
0
                                             false, // aLockInScrollView
2598
0
                                             true,  // aFollowOOFs
2599
0
                                             false // aSkipPopupChecks
2600
0
                                             );
2601
0
          NS_ENSURE_SUCCESS(rv, rv);
2602
0
2603
0
          nsIFrame *newCaretFrame = nullptr;
2604
0
          nsCOMPtr<nsIContent> newCaretContent = startContent;
2605
0
          bool endOfSelectionInStartNode(startContent == endContent);
2606
0
          do {
2607
0
            // Continue getting the next frame until the primary content for the frame
2608
0
            // we are on changes - we don't want to be stuck in the same place
2609
0
            frameTraversal->Next();
2610
0
            newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
2611
0
            if (nullptr == newCaretFrame)
2612
0
              break;
2613
0
            newCaretContent = newCaretFrame->GetContent();
2614
0
          } while (!newCaretContent || newCaretContent == startContent);
2615
0
2616
0
          if (newCaretFrame && newCaretContent) {
2617
0
            // If the caret is exactly at the same position of the new frame,
2618
0
            // then we can use the newCaretFrame and newCaretContent for our position
2619
0
            nsRect caretRect;
2620
0
            nsIFrame *frame = nsCaret::GetGeometry(domSelection, &caretRect);
2621
0
            if (frame) {
2622
0
              nsPoint caretWidgetOffset;
2623
0
              nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
2624
0
              caretRect.MoveBy(caretWidgetOffset);
2625
0
              nsPoint newCaretOffset;
2626
0
              nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
2627
0
              if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
2628
0
                  caretRect.x == newCaretOffset.x) {
2629
0
                // The caret is at the start of the new element.
2630
0
                startFrame = newCaretFrame;
2631
0
                startContent = newCaretContent;
2632
0
                if (endOfSelectionInStartNode) {
2633
0
                  endContent = newCaretContent; // Ensure end of selection is not before start
2634
0
                }
2635
0
              }
2636
0
            }
2637
0
          }
2638
0
        }
2639
0
      }
2640
0
    }
2641
0
  }
2642
0
2643
0
  *aStartContent = startContent;
2644
0
  *aEndContent = endContent;
2645
0
  NS_IF_ADDREF(*aStartContent);
2646
0
  NS_IF_ADDREF(*aEndContent);
2647
0
2648
0
  return NS_OK;
2649
0
}
2650
2651
nsresult
2652
nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
2653
                                            nsIContent* aStartContent,
2654
                                            int32_t aType, bool aNoParentTraversal,
2655
                                            nsIContent** aNextContent)
2656
0
{
2657
0
  *aNextContent = nullptr;
2658
0
2659
0
  // True if we are navigating by document (F6/Shift+F6) or false if we are
2660
0
  // navigating by element (Tab/Shift+Tab).
2661
0
  bool forDocumentNavigation = false;
2662
0
2663
0
  // This is used for document navigation only. It will be set to true if we
2664
0
  // start navigating from a starting point. If this starting point is near the
2665
0
  // end of the document (for example, an element on a statusbar), and there
2666
0
  // are no child documents or panels before the end of the document, then we
2667
0
  // will need to ensure that we don't consider the root chrome window when we
2668
0
  // loop around and instead find the next child document/panel, as focus is
2669
0
  // already in that window. This flag will be cleared once we navigate into
2670
0
  // another document.
2671
0
  bool mayFocusRoot = (aStartContent != nullptr);
2672
0
2673
0
  nsCOMPtr<nsIContent> startContent = aStartContent;
2674
0
  if (!startContent && aType != MOVEFOCUS_CARET) {
2675
0
    if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
2676
0
      // When moving between documents, make sure to get the right
2677
0
      // starting content in a descendant.
2678
0
      nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
2679
0
      startContent = GetFocusedDescendant(aWindow, eIncludeAllDescendants,
2680
0
                                          getter_AddRefs(focusedWindow));
2681
0
    }
2682
0
    else if (aType != MOVEFOCUS_LASTDOC) {
2683
0
      // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
2684
0
      // then we are document-navigating backwards from chrome to the content
2685
0
      // process, and we don't want to use this so that we start from the end
2686
0
      // of the document.
2687
0
      startContent = aWindow->GetFocusedElement();
2688
0
    }
2689
0
  }
2690
0
2691
0
  nsCOMPtr<nsIDocument> doc;
2692
0
  if (startContent)
2693
0
    doc = startContent->GetComposedDoc();
2694
0
  else
2695
0
    doc = aWindow->GetExtantDoc();
2696
0
  if (!doc)
2697
0
    return NS_OK;
2698
0
2699
0
  LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
2700
0
                      &nsIContent::sTabFocusModel);
2701
0
2702
0
  // These types are for document navigation using F6.
2703
0
  if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
2704
0
      aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) {
2705
0
    forDocumentNavigation = true;
2706
0
  }
2707
0
2708
0
  // If moving to the root or first document, find the root element and return.
2709
0
  if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
2710
0
    NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
2711
0
    if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
2712
0
      // When looking for the first document, if the root wasn't focusable,
2713
0
      // find the next focusable document.
2714
0
      aType = MOVEFOCUS_FORWARDDOC;
2715
0
    } else {
2716
0
      return NS_OK;
2717
0
    }
2718
0
  }
2719
0
2720
0
  Element* rootContent = doc->GetRootElement();
2721
0
  NS_ENSURE_TRUE(rootContent, NS_OK);
2722
0
2723
0
  nsIPresShell* presShell = doc->GetShell();
2724
0
  NS_ENSURE_TRUE(presShell, NS_OK);
2725
0
2726
0
  if (aType == MOVEFOCUS_FIRST) {
2727
0
    if (!aStartContent)
2728
0
      startContent = rootContent;
2729
0
    return GetNextTabbableContent(presShell, startContent,
2730
0
                                  nullptr, startContent,
2731
0
                                  true, 1, false, false, aNextContent);
2732
0
  }
2733
0
  if (aType == MOVEFOCUS_LAST) {
2734
0
    if (!aStartContent)
2735
0
      startContent = rootContent;
2736
0
    return GetNextTabbableContent(presShell, startContent,
2737
0
                                  nullptr, startContent,
2738
0
                                  false, 0, false, false, aNextContent);
2739
0
  }
2740
0
2741
0
  bool forward = (aType == MOVEFOCUS_FORWARD ||
2742
0
                  aType == MOVEFOCUS_FORWARDDOC ||
2743
0
                  aType == MOVEFOCUS_CARET);
2744
0
  bool doNavigation = true;
2745
0
  bool ignoreTabIndex = false;
2746
0
  // when a popup is open, we want to ensure that tab navigation occurs only
2747
0
  // within the most recently opened panel. If a popup is open, its frame will
2748
0
  // be stored in popupFrame.
2749
0
  nsIFrame* popupFrame = nullptr;
2750
0
2751
0
  int32_t tabIndex = forward ? 1 : 0;
2752
0
  if (startContent) {
2753
0
    nsIFrame* frame = startContent->GetPrimaryFrame();
2754
0
    if (startContent->IsHTMLElement(nsGkAtoms::area))
2755
0
      startContent->IsFocusable(&tabIndex);
2756
0
    else if (frame)
2757
0
      frame->IsFocusable(&tabIndex, 0);
2758
0
    else
2759
0
      startContent->IsFocusable(&tabIndex);
2760
0
2761
0
    // if the current element isn't tabbable, ignore the tabindex and just
2762
0
    // look for the next element. The root content won't have a tabindex
2763
0
    // so just treat this as the beginning of the tab order.
2764
0
    if (tabIndex < 0) {
2765
0
      tabIndex = 1;
2766
0
      if (startContent != rootContent)
2767
0
        ignoreTabIndex = true;
2768
0
    }
2769
0
2770
0
    // check if the focus is currently inside a popup. Elements such as the
2771
0
    // autocomplete widget use the noautofocus attribute to allow the focus to
2772
0
    // remain outside the popup when it is opened.
2773
0
    if (frame) {
2774
0
      popupFrame =
2775
0
        nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::MenuPopup);
2776
0
    }
2777
0
2778
0
    if (popupFrame && !forDocumentNavigation) {
2779
0
      // Don't navigate outside of a popup, so pretend that the
2780
0
      // root content is the popup itself
2781
0
      rootContent = popupFrame->GetContent()->AsElement();
2782
0
      NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
2783
0
    }
2784
0
    else if (!forward) {
2785
0
      // If focus moves backward and when current focused node is root
2786
0
      // content or <body> element which is editable by contenteditable
2787
0
      // attribute, focus should move to its parent document.
2788
0
      if (startContent == rootContent) {
2789
0
        doNavigation = false;
2790
0
      } else {
2791
0
        nsIDocument* doc = startContent->GetComposedDoc();
2792
0
        if (startContent ==
2793
0
              nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
2794
0
          doNavigation = false;
2795
0
        }
2796
0
      }
2797
0
    }
2798
0
  }
2799
0
  else {
2800
0
#ifdef MOZ_XUL
2801
0
    if (aType != MOVEFOCUS_CARET) {
2802
0
      // if there is no focus, yet a panel is open, focus the first item in
2803
0
      // the panel
2804
0
      nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2805
0
      if (pm)
2806
0
        popupFrame = pm->GetTopPopup(ePopupTypePanel);
2807
0
    }
2808
0
#endif
2809
0
    if (popupFrame) {
2810
0
      // When there is a popup open, and no starting content, start the search
2811
0
      // at the topmost popup.
2812
0
      startContent = popupFrame->GetContent();
2813
0
      NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
2814
0
      // Unless we are searching for documents, set the root content to the
2815
0
      // popup as well, so that we don't tab-navigate outside the popup.
2816
0
      // When navigating by documents, we start at the popup but can navigate
2817
0
      // outside of it to look for other panels and documents.
2818
0
      if (!forDocumentNavigation) {
2819
0
        rootContent = startContent->AsElement();
2820
0
      }
2821
0
2822
0
      doc = startContent ? startContent->GetComposedDoc() : nullptr;
2823
0
    }
2824
0
    else {
2825
0
      // Otherwise, for content shells, start from the location of the caret.
2826
0
      nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
2827
0
      if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
2828
0
        nsCOMPtr<nsIContent> endSelectionContent;
2829
0
        GetSelectionLocation(doc, presShell,
2830
0
                             getter_AddRefs(startContent),
2831
0
                             getter_AddRefs(endSelectionContent));
2832
0
        // If the selection is on the rootContent, then there is no selection
2833
0
        if (startContent == rootContent) {
2834
0
          startContent = nullptr;
2835
0
        }
2836
0
2837
0
        if (aType == MOVEFOCUS_CARET) {
2838
0
          // GetFocusInSelection finds a focusable link near the caret.
2839
0
          // If there is no start content though, don't do this to avoid
2840
0
          // focusing something unexpected.
2841
0
          if (startContent) {
2842
0
            GetFocusInSelection(aWindow, startContent,
2843
0
                                endSelectionContent, aNextContent);
2844
0
          }
2845
0
          return NS_OK;
2846
0
        }
2847
0
2848
0
        if (startContent) {
2849
0
          // when starting from a selection, we always want to find the next or
2850
0
          // previous element in the document. So the tabindex on elements
2851
0
          // should be ignored.
2852
0
          ignoreTabIndex = true;
2853
0
        }
2854
0
      }
2855
0
2856
0
      if (!startContent) {
2857
0
        // otherwise, just use the root content as the starting point
2858
0
        startContent = rootContent;
2859
0
        NS_ENSURE_TRUE(startContent, NS_OK);
2860
0
      }
2861
0
    }
2862
0
  }
2863
0
2864
0
  // Check if the starting content is the same as the content assigned to the
2865
0
  // retargetdocumentfocus attribute. Is so, we don't want to start searching
2866
0
  // from there but instead from the beginning of the document. Otherwise, the
2867
0
  // content that appears before the retargetdocumentfocus element will never
2868
0
  // get checked as it will be skipped when the focus is retargetted to it.
2869
0
  if (forDocumentNavigation && doc->IsXULDocument()) {
2870
0
    nsAutoString retarget;
2871
0
2872
0
    if (rootContent->GetAttr(kNameSpaceID_None,
2873
0
                             nsGkAtoms::retargetdocumentfocus, retarget)) {
2874
0
      nsIContent* retargetElement = doc->GetElementById(retarget);
2875
0
      // The common case here is the urlbar where focus is on the anonymous
2876
0
      // input inside the textbox, but the retargetdocumentfocus attribute
2877
0
      // refers to the textbox. The Contains check will return false and the
2878
0
      // ContentIsDescendantOf check will return true in this case.
2879
0
      if (retargetElement && (retargetElement == startContent ||
2880
0
                              (!retargetElement->Contains(startContent) &&
2881
0
                              nsContentUtils::ContentIsDescendantOf(startContent, retargetElement)))) {
2882
0
        startContent = rootContent;
2883
0
      }
2884
0
    }
2885
0
  }
2886
0
2887
0
  NS_ASSERTION(startContent, "starting content not set");
2888
0
2889
0
  // keep a reference to the starting content. If we find that again, it means
2890
0
  // we've iterated around completely and we don't want to adjust the focus.
2891
0
  // The skipOriginalContentCheck will be set to true only for the first time
2892
0
  // GetNextTabbableContent is called. This ensures that we don't break out
2893
0
  // when nothing is focused to start with. Specifically,
2894
0
  // GetNextTabbableContent first checks the root content -- which happens to
2895
0
  // be the same as the start content -- when nothing is focused and tabbing
2896
0
  // forward. Without skipOriginalContentCheck set to true, we'd end up
2897
0
  // returning right away and focusing nothing. Luckily, GetNextTabbableContent
2898
0
  // will never wrap around on its own, and can only return the original
2899
0
  // content when it is called a second time or later.
2900
0
  bool skipOriginalContentCheck = true;
2901
0
  nsIContent* originalStartContent = startContent;
2902
0
2903
0
  LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
2904
0
  LOGFOCUSNAVIGATION(("  Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
2905
0
                      forward, tabIndex, ignoreTabIndex, forDocumentNavigation));
2906
0
2907
0
  while (doc) {
2908
0
    if (doNavigation) {
2909
0
      nsCOMPtr<nsIContent> nextFocus;
2910
0
      nsresult rv = GetNextTabbableContent(presShell, rootContent,
2911
0
                                           skipOriginalContentCheck ? nullptr : originalStartContent,
2912
0
                                           startContent, forward,
2913
0
                                           tabIndex, ignoreTabIndex,
2914
0
                                           forDocumentNavigation,
2915
0
                                           getter_AddRefs(nextFocus));
2916
0
      NS_ENSURE_SUCCESS(rv, rv);
2917
0
      if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
2918
0
        // Navigation was redirected to a child process, so just return.
2919
0
        return NS_OK;
2920
0
      }
2921
0
2922
0
      // found a content node to focus.
2923
0
      if (nextFocus) {
2924
0
        LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
2925
0
2926
0
        // as long as the found node was not the same as the starting node,
2927
0
        // set it as the return value. For document navigation, we can return
2928
0
        // the same element in case there is only one content node that could
2929
0
        // be returned, for example, in a child process document.
2930
0
        if (nextFocus != originalStartContent || forDocumentNavigation) {
2931
0
          nextFocus.forget(aNextContent);
2932
0
        }
2933
0
        return NS_OK;
2934
0
      }
2935
0
2936
0
      if (popupFrame && !forDocumentNavigation) {
2937
0
        // in a popup, so start again from the beginning of the popup. However,
2938
0
        // if we already started at the beginning, then there isn't anything to
2939
0
        // focus, so just return
2940
0
        if (startContent != rootContent) {
2941
0
          startContent = rootContent;
2942
0
          tabIndex = forward ? 1 : 0;
2943
0
          continue;
2944
0
        }
2945
0
        return NS_OK;
2946
0
      }
2947
0
    }
2948
0
2949
0
    doNavigation = true;
2950
0
    skipOriginalContentCheck = forDocumentNavigation;
2951
0
    ignoreTabIndex = false;
2952
0
2953
0
    if (aNoParentTraversal) {
2954
0
      if (startContent == rootContent)
2955
0
        return NS_OK;
2956
0
2957
0
      startContent = rootContent;
2958
0
      tabIndex = forward ? 1 : 0;
2959
0
      continue;
2960
0
    }
2961
0
2962
0
    // Reached the beginning or end of the document. Next, navigate up to the
2963
0
    // parent document and try again.
2964
0
    nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
2965
0
    NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
2966
0
2967
0
    nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
2968
0
    NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
2969
0
2970
0
    // Get the frame element this window is inside and, from that, get the
2971
0
    // parent document and presshell. If there is no enclosing frame element,
2972
0
    // then this is a top-level, embedded or remote window.
2973
0
    startContent = piWindow->GetFrameElementInternal();
2974
0
    if (startContent) {
2975
0
      doc = startContent->GetComposedDoc();
2976
0
      NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
2977
0
2978
0
      rootContent = doc->GetRootElement();
2979
0
      presShell = doc->GetShell();
2980
0
2981
0
      // We can focus the root element now that we have moved to another document.
2982
0
      mayFocusRoot = true;
2983
0
2984
0
      nsIFrame* frame = startContent->GetPrimaryFrame();
2985
0
      if (!frame) {
2986
0
        return NS_OK;
2987
0
      }
2988
0
2989
0
      frame->IsFocusable(&tabIndex, 0);
2990
0
      if (tabIndex < 0) {
2991
0
        tabIndex = 1;
2992
0
        ignoreTabIndex = true;
2993
0
      }
2994
0
2995
0
      // if the frame is inside a popup, make sure to scan only within the
2996
0
      // popup. This handles the situation of tabbing amongst elements
2997
0
      // inside an iframe which is itself inside a popup. Otherwise,
2998
0
      // navigation would move outside the popup when tabbing outside the
2999
0
      // iframe.
3000
0
      if (!forDocumentNavigation) {
3001
0
        popupFrame = nsLayoutUtils::GetClosestFrameOfType(
3002
0
          frame, LayoutFrameType::MenuPopup);
3003
0
        if (popupFrame) {
3004
0
          rootContent = popupFrame->GetContent()->AsElement();
3005
0
          NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
3006
0
        }
3007
0
      }
3008
0
    }
3009
0
    else {
3010
0
      // There is no parent, so call the tree owner. This will tell the
3011
0
      // embedder or parent process that it should take the focus.
3012
0
      bool tookFocus;
3013
0
      docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
3014
0
      // If the tree owner took the focus, blur the current element.
3015
0
      if (tookFocus) {
3016
0
        nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
3017
0
        if (window->GetFocusedElement() == mFocusedElement)
3018
0
          Blur(mFocusedWindow, nullptr, true, true);
3019
0
        else
3020
0
          window->SetFocusedElement(nullptr);
3021
0
        return NS_OK;
3022
0
      }
3023
0
3024
0
      // If we have reached the end of the top-level document, focus the
3025
0
      // first element in the top-level document. This should always happen
3026
0
      // when navigating by document forwards but when navigating backwards,
3027
0
      // only do this if we started in another document or within a popup frame.
3028
0
      // If the focus started in this window outside a popup however, we should
3029
0
      // continue by looping around to the end again.
3030
0
      if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
3031
0
        // HTML content documents can have their root element focused (a focus
3032
0
        // ring appears around the entire content area frame). This root
3033
0
        // appears in the tab order before all of the elements in the document.
3034
0
        // Chrome documents however cannot be focused directly, so instead we
3035
0
        // focus the first focusable element within the window.
3036
0
        // For example, the urlbar.
3037
0
        Element* root = GetRootForFocus(piWindow, doc, true, true);
3038
0
        return FocusFirst(root, aNextContent);
3039
0
      }
3040
0
3041
0
      // Once we have hit the top-level and have iterated to the end again, we
3042
0
      // just want to break out next time we hit this spot to prevent infinite
3043
0
      // iteration.
3044
0
      mayFocusRoot = true;
3045
0
3046
0
      // reset the tab index and start again from the beginning or end
3047
0
      startContent = rootContent;
3048
0
      tabIndex = forward ? 1 : 0;
3049
0
    }
3050
0
3051
0
    // wrapped all the way around and didn't find anything to move the focus
3052
0
    // to, so just break out
3053
0
    if (startContent == originalStartContent)
3054
0
      break;
3055
0
  }
3056
0
3057
0
  return NS_OK;
3058
0
}
3059
3060
// Helper class to iterate contents in scope by traversing flattened tree
3061
// in tree order
3062
class MOZ_STACK_CLASS ScopedContentTraversal
3063
{
3064
public:
3065
  ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner)
3066
    : mCurrent(aStartContent)
3067
    , mOwner(aOwner)
3068
0
  {
3069
0
    MOZ_ASSERT(aStartContent);
3070
0
  }
3071
3072
  void Next();
3073
  void Prev();
3074
3075
  void Reset()
3076
0
  {
3077
0
    SetCurrent(mOwner);
3078
0
  }
3079
3080
  nsIContent* GetCurrent()
3081
0
  {
3082
0
    return mCurrent;
3083
0
  }
3084
3085
private:
3086
  void SetCurrent(nsIContent* aContent)
3087
0
  {
3088
0
    mCurrent = aContent;
3089
0
  }
3090
3091
  nsIContent* mCurrent;
3092
  nsIContent* mOwner;
3093
};
3094
3095
void
3096
ScopedContentTraversal::Next()
3097
0
{
3098
0
  MOZ_ASSERT(mCurrent);
3099
0
3100
0
  // Get mCurrent's first child if it's in the same scope.
3101
0
  if (!(mCurrent->GetShadowRoot() || mCurrent->IsHTMLElement(nsGkAtoms::slot)) ||
3102
0
      mCurrent == mOwner) {
3103
0
    FlattenedChildIterator iter(mCurrent);
3104
0
    nsIContent* child = iter.GetNextChild();
3105
0
    if (child) {
3106
0
      SetCurrent(child);
3107
0
      return;
3108
0
    }
3109
0
  }
3110
0
3111
0
  // If mOwner has no children, END traversal
3112
0
  if (mCurrent == mOwner) {
3113
0
    SetCurrent(nullptr);
3114
0
    return;
3115
0
  }
3116
0
3117
0
  nsIContent* current = mCurrent;
3118
0
  while (1) {
3119
0
    // Create parent's iterator and move to current
3120
0
    nsIContent* parent = current->GetFlattenedTreeParent();
3121
0
    FlattenedChildIterator parentIter(parent);
3122
0
    parentIter.Seek(current);
3123
0
3124
0
    // Get next sibling of current
3125
0
    nsIContent* next = parentIter.GetNextChild();
3126
0
    if (next) {
3127
0
      SetCurrent(next);
3128
0
      return;
3129
0
    }
3130
0
3131
0
    // If no next sibling and parent is mOwner, END traversal
3132
0
    if (parent == mOwner) {
3133
0
      SetCurrent(nullptr);
3134
0
      return;
3135
0
    }
3136
0
3137
0
    current = parent;
3138
0
  }
3139
0
}
3140
3141
void
3142
ScopedContentTraversal::Prev()
3143
0
{
3144
0
  MOZ_ASSERT(mCurrent);
3145
0
3146
0
  nsIContent* parent;
3147
0
  nsIContent* last;
3148
0
  if (mCurrent == mOwner) {
3149
0
    // Get last child of mOwner
3150
0
    FlattenedChildIterator ownerIter(mOwner, false /* aStartAtBeginning */);
3151
0
    last = ownerIter.GetPreviousChild();
3152
0
3153
0
    parent = last;
3154
0
  } else {
3155
0
    // Create parent's iterator and move to mCurrent
3156
0
    parent = mCurrent->GetFlattenedTreeParent();
3157
0
    FlattenedChildIterator parentIter(parent);
3158
0
    parentIter.Seek(mCurrent);
3159
0
3160
0
    // Get previous sibling
3161
0
    last = parentIter.GetPreviousChild();
3162
0
  }
3163
0
3164
0
  while (last) {
3165
0
    parent = last;
3166
0
    if (parent->GetShadowRoot() ||
3167
0
        parent->IsHTMLElement(nsGkAtoms::slot)) {
3168
0
      // Skip contents in other scopes
3169
0
      break;
3170
0
    }
3171
0
3172
0
    // Find last child
3173
0
    FlattenedChildIterator iter(parent, false /* aStartAtBeginning */);
3174
0
    last = iter.GetPreviousChild();
3175
0
  }
3176
0
3177
0
  // If parent is mOwner and no previous sibling remains, END traversal
3178
0
  SetCurrent(parent == mOwner ? nullptr : parent);
3179
0
}
3180
3181
nsIContent*
3182
nsFocusManager::FindOwner(nsIContent* aContent)
3183
0
{
3184
0
  nsIContent* currentContent = aContent;
3185
0
  while (currentContent) {
3186
0
    nsIContent* parent = currentContent->GetFlattenedTreeParent();
3187
0
    if (!parent) {
3188
0
      // Document root
3189
0
      nsIDocument* doc = currentContent->GetUncomposedDoc();
3190
0
      if (doc && doc->GetRootElement() == currentContent) {
3191
0
        return currentContent;
3192
0
      }
3193
0
3194
0
      break;
3195
0
    }
3196
0
3197
0
    // Shadow host / Slot
3198
0
    if (IsHostOrSlot(parent)) {
3199
0
      return parent;
3200
0
    }
3201
0
3202
0
    currentContent = parent;
3203
0
  }
3204
0
3205
0
  return nullptr;
3206
0
}
3207
3208
bool
3209
nsFocusManager::IsHostOrSlot(nsIContent* aContent)
3210
0
{
3211
0
  return aContent->GetShadowRoot() || // shadow host
3212
0
         aContent->IsHTMLElement(nsGkAtoms::slot); // slot
3213
0
}
3214
3215
int32_t
3216
nsFocusManager::HostOrSlotTabIndexValue(nsIContent* aContent,
3217
                                        bool* aIsFocusable)
3218
0
{
3219
0
  MOZ_ASSERT(IsHostOrSlot(aContent));
3220
0
3221
0
  if (aIsFocusable) {
3222
0
    *aIsFocusable = false;
3223
0
    nsIFrame* frame = aContent->GetPrimaryFrame();
3224
0
    if (frame) {
3225
0
      int32_t tabIndex;
3226
0
      frame->IsFocusable(&tabIndex, 0);
3227
0
      *aIsFocusable = tabIndex >= 0;
3228
0
    }
3229
0
  }
3230
0
3231
0
  const nsAttrValue* attrVal =
3232
0
    aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
3233
0
  if (!attrVal) {
3234
0
    return 0;
3235
0
  }
3236
0
3237
0
  if (attrVal->Type() == nsAttrValue::eInteger) {
3238
0
    return attrVal->GetIntegerValue();
3239
0
  }
3240
0
3241
0
  return -1;
3242
0
}
3243
3244
nsIContent*
3245
nsFocusManager::GetNextTabbableContentInScope(nsIContent* aOwner,
3246
                                              nsIContent* aStartContent,
3247
                                              nsIContent* aOriginalStartContent,
3248
                                              bool aForward,
3249
                                              int32_t aCurrentTabIndex,
3250
                                              bool aIgnoreTabIndex,
3251
                                              bool aForDocumentNavigation,
3252
                                              bool aSkipOwner)
3253
0
{
3254
0
  // Return shadow host at first for forward navigation if its tabindex
3255
0
  // is non-negative
3256
0
  bool skipOwner = aSkipOwner || !aOwner->GetShadowRoot();
3257
0
  if (!skipOwner && (aForward && aOwner == aStartContent)) {
3258
0
    int32_t tabIndex = 0;
3259
0
    aOwner->IsFocusable(&tabIndex);
3260
0
    if (tabIndex >= 0) {
3261
0
      return aOwner;
3262
0
    }
3263
0
  }
3264
0
3265
0
  //
3266
0
  // Iterate contents in scope
3267
0
  //
3268
0
  ScopedContentTraversal contentTraversal(aStartContent, aOwner);
3269
0
  nsCOMPtr<nsIContent> iterContent;
3270
0
  nsIContent* firstNonChromeOnly = aStartContent->IsInNativeAnonymousSubtree() ?
3271
0
    aStartContent->FindFirstNonChromeOnlyAccessContent() : nullptr;
3272
0
  while (1) {
3273
0
    // Iterate tab index to find corresponding contents in scope
3274
0
3275
0
    while (1) {
3276
0
      // Iterate remaining contents in scope to find next content to focus
3277
0
3278
0
      // Get next content
3279
0
      aForward ? contentTraversal.Next() : contentTraversal.Prev();
3280
0
      iterContent = contentTraversal.GetCurrent();
3281
0
3282
0
      if (firstNonChromeOnly && firstNonChromeOnly == iterContent) {
3283
0
        // We just broke out from the native anonynous content, so move
3284
0
        // to the previous/next node of the native anonymous owner.
3285
0
        if (aForward) {
3286
0
          contentTraversal.Next();
3287
0
        } else {
3288
0
          contentTraversal.Prev();
3289
0
        }
3290
0
        iterContent = contentTraversal.GetCurrent();
3291
0
      }
3292
0
      if (!iterContent) {
3293
0
        // Reach the end
3294
0
        break;
3295
0
      }
3296
0
3297
0
      int32_t tabIndex = 0;
3298
0
      if (iterContent->IsInNativeAnonymousSubtree() &&
3299
0
          iterContent->GetPrimaryFrame()) {
3300
0
        iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex);
3301
0
      } else if (IsHostOrSlot(iterContent)) {
3302
0
        tabIndex = HostOrSlotTabIndexValue(iterContent);
3303
0
      } else {
3304
0
        nsIFrame* frame = iterContent->GetPrimaryFrame();
3305
0
        if (!frame) {
3306
0
          continue;
3307
0
        }
3308
0
        frame->IsFocusable(&tabIndex, 0);
3309
0
      }
3310
0
      if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
3311
0
        // If the element has native anonymous content, we may need to
3312
0
        // focus some NAC element, even if the element itself isn't focusable.
3313
0
        // This happens for example with <input type="date">.
3314
0
        // So, try to find NAC and then traverse the frame tree to find elements
3315
0
        // to focus.
3316
0
        nsIFrame* possibleAnonOwnerFrame = iterContent->GetPrimaryFrame();
3317
0
        nsIAnonymousContentCreator* anonCreator =
3318
0
          do_QueryFrame(possibleAnonOwnerFrame);
3319
0
        if (anonCreator && !iterContent->IsInNativeAnonymousSubtree()) {
3320
0
          nsIFrame* frame = nullptr;
3321
0
          // Find the first or last frame in tree order so that
3322
0
          // we can scope frame traversing to NAC.
3323
0
          if (aForward) {
3324
0
            frame = possibleAnonOwnerFrame->PrincipalChildList().FirstChild();
3325
0
          } else {
3326
0
            frame = possibleAnonOwnerFrame->PrincipalChildList().LastChild();
3327
0
            nsIFrame* last = frame;
3328
0
            while (last) {
3329
0
              frame = last;
3330
0
              last = frame->PrincipalChildList().LastChild();
3331
0
            }
3332
0
          };
3333
0
3334
0
          nsCOMPtr<nsIFrameEnumerator> frameTraversal;
3335
0
          nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
3336
0
                                             iterContent->OwnerDoc()->
3337
0
                                               GetShell()->GetPresContext(),
3338
0
                                             frame,
3339
0
                                             ePreOrder,
3340
0
                                             false, // aVisual
3341
0
                                             false, // aLockInScrollView
3342
0
                                             true, // aFollowOOFs
3343
0
                                             true  // aSkipPopupChecks
3344
0
                                             );
3345
0
          if (NS_SUCCEEDED(rv)) {
3346
0
            nsIFrame* frame =
3347
0
              static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3348
0
            while (frame) {
3349
0
              int32_t tabIndex;
3350
0
              frame->IsFocusable(&tabIndex, 0);
3351
0
              if (tabIndex >= 0 &&
3352
0
                  (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
3353
0
                return frame->GetContent();
3354
0
              }
3355
0
3356
0
              if (aForward) {
3357
0
                frameTraversal->Next();
3358
0
              } else {
3359
0
                frameTraversal->Prev();
3360
0
              }
3361
0
              frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3362
0
              if (frame == possibleAnonOwnerFrame) {
3363
0
                break;
3364
0
              }
3365
0
            }
3366
0
          }
3367
0
        }
3368
0
3369
0
        continue;
3370
0
      }
3371
0
3372
0
      if (!IsHostOrSlot(iterContent)) {
3373
0
        nsCOMPtr<nsIContent> elementInFrame;
3374
0
        bool checkSubDocument = true;
3375
0
        if (aForDocumentNavigation &&
3376
0
            TryDocumentNavigation(iterContent, &checkSubDocument,
3377
0
                                  getter_AddRefs(elementInFrame))) {
3378
0
          return elementInFrame;
3379
0
        }
3380
0
        if (!checkSubDocument) {
3381
0
          continue;
3382
0
        }
3383
0
3384
0
        if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent,
3385
0
                                        aForward, aForDocumentNavigation,
3386
0
                                        getter_AddRefs(elementInFrame))) {
3387
0
          return elementInFrame;
3388
0
        }
3389
0
3390
0
        // Found content to focus
3391
0
        return iterContent;
3392
0
      }
3393
0
3394
0
      // Search in scope owned by iterContent
3395
0
      nsIContent* contentToFocus =
3396
0
        GetNextTabbableContentInScope(iterContent, iterContent,
3397
0
                                      aOriginalStartContent, aForward,
3398
0
                                      aForward ? 1 : 0, aIgnoreTabIndex,
3399
0
                                      aForDocumentNavigation,
3400
0
                                      false /* aSkipOwner */);
3401
0
      if (contentToFocus) {
3402
0
        return contentToFocus;
3403
0
      }
3404
0
    };
3405
0
3406
0
    // If already at lowest priority tab (0), end search completely.
3407
0
    // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3408
0
    if (aCurrentTabIndex == (aForward ? 0 : 1)) {
3409
0
      break;
3410
0
    }
3411
0
3412
0
    // Continue looking for next highest priority tabindex
3413
0
    aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
3414
0
    contentTraversal.Reset();
3415
0
  }
3416
0
3417
0
  // Return shadow host at last for backward navigation if its tabindex
3418
0
  // is non-negative
3419
0
  if (!skipOwner && !aForward) {
3420
0
    int32_t tabIndex = 0;
3421
0
    aOwner->IsFocusable(&tabIndex);
3422
0
    if (tabIndex >= 0) {
3423
0
      return aOwner;
3424
0
    }
3425
0
  }
3426
0
3427
0
  return nullptr;
3428
0
}
3429
3430
nsIContent*
3431
nsFocusManager::GetNextTabbableContentInAncestorScopes(
3432
  nsIContent** aStartContent,
3433
  nsIContent* aOriginalStartContent,
3434
  bool aForward,
3435
  int32_t* aCurrentTabIndex,
3436
  bool aIgnoreTabIndex,
3437
  bool aForDocumentNavigation)
3438
0
{
3439
0
  nsIContent* startContent = *aStartContent;
3440
0
  while (1) {
3441
0
    nsIContent* owner = FindOwner(startContent);
3442
0
    MOZ_ASSERT(owner, "focus navigation scope owner not in document");
3443
0
3444
0
    int32_t tabIndex = 0;
3445
0
    if (IsHostOrSlot(startContent)) {
3446
0
      tabIndex = HostOrSlotTabIndexValue(startContent);
3447
0
    } else {
3448
0
      startContent->IsFocusable(&tabIndex);
3449
0
    }
3450
0
    nsIContent* contentToFocus =
3451
0
      GetNextTabbableContentInScope(owner, startContent, aOriginalStartContent,
3452
0
                                    aForward, tabIndex, aIgnoreTabIndex,
3453
0
                                    aForDocumentNavigation,
3454
0
                                    false /* aSkipOwner */);
3455
0
    if (contentToFocus) {
3456
0
      return contentToFocus;
3457
0
    }
3458
0
3459
0
    // If not found in shadow DOM, search from the shadow host in light DOM
3460
0
    if (!owner->IsInShadowTree()) {
3461
0
      MOZ_ASSERT(owner->GetShadowRoot());
3462
0
3463
0
      *aStartContent = owner;
3464
0
      *aCurrentTabIndex = HostOrSlotTabIndexValue(owner);
3465
0
      break;
3466
0
    }
3467
0
3468
0
    startContent = owner;
3469
0
  }
3470
0
3471
0
  return nullptr;
3472
0
}
3473
3474
static nsIContent*
3475
GetTopLevelHost(nsIContent* aContent)
3476
0
{
3477
0
  nsIContent* topLevelhost = nullptr;
3478
0
  while (aContent) {
3479
0
    if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
3480
0
      aContent = slot;
3481
0
    } else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) {
3482
0
      aContent = shadowRoot->Host();
3483
0
      topLevelhost = aContent;
3484
0
    } else {
3485
0
      aContent = aContent->GetParent();
3486
0
    }
3487
0
  }
3488
0
3489
0
  return topLevelhost;
3490
0
}
3491
3492
nsresult
3493
nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
3494
                                       nsIContent* aRootContent,
3495
                                       nsIContent* aOriginalStartContent,
3496
                                       nsIContent* aStartContent,
3497
                                       bool aForward,
3498
                                       int32_t aCurrentTabIndex,
3499
                                       bool aIgnoreTabIndex,
3500
                                       bool aForDocumentNavigation,
3501
                                       nsIContent** aResultContent)
3502
0
{
3503
0
  *aResultContent = nullptr;
3504
0
3505
0
  nsCOMPtr<nsIContent> startContent = aStartContent;
3506
0
  if (!startContent)
3507
0
    return NS_OK;
3508
0
3509
0
  nsIContent* currentTopLevelHost = GetTopLevelHost(aStartContent);
3510
0
3511
0
  LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
3512
0
  LOGFOCUSNAVIGATION(("  tabindex: %d", aCurrentTabIndex));
3513
0
3514
0
  if (nsDocument::IsShadowDOMEnabled(aRootContent)) {
3515
0
    // If aStartContent is a shadow host or slot in forward navigation,
3516
0
    // search in scope owned by aStartContent
3517
0
    if (aForward && IsHostOrSlot(aStartContent)) {
3518
0
      nsIContent* contentToFocus =
3519
0
        GetNextTabbableContentInScope(aStartContent, aStartContent,
3520
0
                                      aOriginalStartContent, aForward,
3521
0
                                      aForward ? 1 : 0, aIgnoreTabIndex,
3522
0
                                      aForDocumentNavigation,
3523
0
                                      true /* aSkipOwner */);
3524
0
      if (contentToFocus) {
3525
0
        NS_ADDREF(*aResultContent = contentToFocus);
3526
0
        return NS_OK;
3527
0
      }
3528
0
    }
3529
0
3530
0
    // If aStartContent is not in a scope owned by the root element
3531
0
    // (i.e. aStartContent is already in shadow DOM),
3532
0
    // search from scope including aStartContent
3533
0
    nsIContent* rootElement = aRootContent->OwnerDoc()->GetRootElement();
3534
0
    nsIContent* owner = FindOwner(aStartContent);
3535
0
    if (owner && rootElement != owner) {
3536
0
      nsIContent* contentToFocus =
3537
0
        GetNextTabbableContentInAncestorScopes(&aStartContent,
3538
0
                                               aOriginalStartContent,
3539
0
                                               aForward,
3540
0
                                               &aCurrentTabIndex,
3541
0
                                               aIgnoreTabIndex,
3542
0
                                               aForDocumentNavigation);
3543
0
      if (contentToFocus) {
3544
0
        NS_ADDREF(*aResultContent = contentToFocus);
3545
0
        return NS_OK;
3546
0
      }
3547
0
    }
3548
0
3549
0
    // If we reach here, it means no next tabbable content in shadow DOM.
3550
0
    // We need to continue searching in light DOM, starting at the shadow host
3551
0
    // in light DOM (updated aStartContent) and its tabindex
3552
0
    // (updated aCurrentTabIndex).
3553
0
  }
3554
0
3555
0
  nsPresContext* presContext = aPresShell->GetPresContext();
3556
0
3557
0
  bool getNextFrame = true;
3558
0
  nsCOMPtr<nsIContent> iterStartContent = aStartContent;
3559
0
  while (1) {
3560
0
    nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
3561
0
    // if there is no frame, look for another content node that has a frame
3562
0
    if (!startFrame) {
3563
0
      // if the root content doesn't have a frame, just return
3564
0
      if (iterStartContent == aRootContent)
3565
0
        return NS_OK;
3566
0
3567
0
      // look for the next or previous content node in tree order
3568
0
      iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent();
3569
0
      // we've already skipped over the initial focused content, so we
3570
0
      // don't want to traverse frames.
3571
0
      getNextFrame = false;
3572
0
      if (iterStartContent)
3573
0
        continue;
3574
0
3575
0
      // otherwise, as a last attempt, just look at the root content
3576
0
      iterStartContent = aRootContent;
3577
0
      continue;
3578
0
    }
3579
0
3580
0
    // For tab navigation, pass false for aSkipPopupChecks so that we don't
3581
0
    // iterate into or out of a popup. For document naviation pass true to
3582
0
    // ignore these boundaries.
3583
0
    nsCOMPtr<nsIFrameEnumerator> frameTraversal;
3584
0
    nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
3585
0
                                       presContext, startFrame,
3586
0
                                       ePreOrder,
3587
0
                                       false, // aVisual
3588
0
                                       false, // aLockInScrollView
3589
0
                                       true,  // aFollowOOFs
3590
0
                                       aForDocumentNavigation  // aSkipPopupChecks
3591
0
                                       );
3592
0
    NS_ENSURE_SUCCESS(rv, rv);
3593
0
3594
0
    if (iterStartContent == aRootContent) {
3595
0
      if (!aForward) {
3596
0
        frameTraversal->Last();
3597
0
      } else if (aRootContent->IsFocusable()) {
3598
0
        frameTraversal->Next();
3599
0
      }
3600
0
    }
3601
0
    else if (getNextFrame &&
3602
0
             (!iterStartContent ||
3603
0
              !iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
3604
0
      // Need to do special check in case we're in an imagemap which has multiple
3605
0
      // content nodes per frame, so don't skip over the starting frame.
3606
0
      if (aForward)
3607
0
        frameTraversal->Next();
3608
0
      else
3609
0
        frameTraversal->Prev();
3610
0
    }
3611
0
3612
0
    // Walk frames to find something tabbable matching mCurrentTabIndex
3613
0
    nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3614
0
    while (frame) {
3615
0
      // Try to find the topmost Shadow DOM host, since we want to
3616
0
      // skip Shadow DOM in frame traversal.
3617
0
      nsIContent* currentContent = frame->GetContent();
3618
0
      nsIContent* oldTopLevelHost = currentTopLevelHost;
3619
0
      if (oldTopLevelHost != currentContent) {
3620
0
        currentTopLevelHost = GetTopLevelHost(currentContent);
3621
0
      } else {
3622
0
        currentTopLevelHost = currentContent;
3623
0
      }
3624
0
      if (currentTopLevelHost) {
3625
0
        if (currentTopLevelHost == oldTopLevelHost) {
3626
0
          // We're within Shadow DOM, continue.
3627
0
          do {
3628
0
            if (aForward) {
3629
0
              frameTraversal->Next();
3630
0
            } else {
3631
0
              frameTraversal->Prev();
3632
0
            }
3633
0
            frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3634
0
            // For the usage of GetPrevContinuation, see the comment
3635
0
            // at the end of while (frame) loop.
3636
0
          } while (frame && frame->GetPrevContinuation());
3637
0
          continue;
3638
0
        }
3639
0
        currentContent = currentTopLevelHost;
3640
0
      }
3641
0
3642
0
      // For document navigation, check if this element is an open panel. Since
3643
0
      // panels aren't focusable (tabIndex would be -1), we'll just assume that
3644
0
      // for document navigation, the tabIndex is 0.
3645
0
      if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
3646
0
          currentContent->IsXULElement(nsGkAtoms::panel)) {
3647
0
        nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
3648
0
        // Check if the panel is open. Closed panels are ignored since you can't
3649
0
        // focus anything in them.
3650
0
        if (popupFrame && popupFrame->IsOpen()) {
3651
0
          // When moving backward, skip the popup we started in otherwise it
3652
0
          // will be selected again.
3653
0
          bool validPopup = true;
3654
0
          if (!aForward) {
3655
0
            nsIContent* content = aStartContent;
3656
0
            while (content) {
3657
0
              if (content == currentContent) {
3658
0
                validPopup = false;
3659
0
                break;
3660
0
              }
3661
0
3662
0
              content = content->GetParent();
3663
0
            }
3664
0
          }
3665
0
3666
0
          if (validPopup) {
3667
0
            // Since a panel isn't focusable itself, find the first focusable
3668
0
            // content within the popup. If there isn't any focusable content
3669
0
            // in the popup, skip this popup and continue iterating through the
3670
0
            // frames. We pass the panel itself (currentContent) as the starting
3671
0
            // and root content, so that we only find content within the panel.
3672
0
            // Note also that we pass false for aForDocumentNavigation since we
3673
0
            // want to locate the first content, not the first document.
3674
0
            rv = GetNextTabbableContent(aPresShell, currentContent,
3675
0
                                        nullptr, currentContent,
3676
0
                                        true, 1, false, false,
3677
0
                                        aResultContent);
3678
0
            if (NS_SUCCEEDED(rv) && *aResultContent) {
3679
0
              return rv;
3680
0
            }
3681
0
          }
3682
0
        }
3683
0
      }
3684
0
3685
0
      // As of now, 2018/04/12, sequential focus navigation is still
3686
0
      // in the obsolete Shadow DOM specification.
3687
0
      // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
3688
0
      // "if ELEMENT is focusable, a shadow host, or a slot element,
3689
0
      //  append ELEMENT to NAVIGATION-ORDER."
3690
0
      // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
3691
0
      // hosts and slots are handled before other elements.
3692
0
      if (currentContent && nsDocument::IsShadowDOMEnabled(currentContent) &&
3693
0
          IsHostOrSlot(currentContent)) {
3694
0
        bool focusableHostSlot;
3695
0
        int32_t tabIndex = HostOrSlotTabIndexValue(currentContent,
3696
0
                                                   &focusableHostSlot);
3697
0
        // Host or slot itself isn't focusable, enter its scope.
3698
0
        if (!focusableHostSlot &&
3699
0
            tabIndex >= 0 &&
3700
0
            (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
3701
0
          nsIContent* contentToFocus =
3702
0
            GetNextTabbableContentInScope(currentContent, currentContent,
3703
0
                                          aOriginalStartContent, aForward,
3704
0
                                          aForward ? 1 : 0, aIgnoreTabIndex,
3705
0
                                          aForDocumentNavigation,
3706
0
                                          true /* aSkipOwner */);
3707
0
          if (contentToFocus) {
3708
0
            NS_ADDREF(*aResultContent = contentToFocus);
3709
0
            return NS_OK;
3710
0
          }
3711
0
        }
3712
0
      }
3713
0
3714
0
      // TabIndex not set defaults to 0 for form elements, anchors and other
3715
0
      // elements that are normally focusable. Tabindex defaults to -1
3716
0
      // for elements that are not normally focusable.
3717
0
      // The returned computed tabindex from IsFocusable() is as follows:
3718
0
      //          < 0 not tabbable at all
3719
0
      //          == 0 in normal tab order (last after positive tabindexed items)
3720
0
      //          > 0 can be tabbed to in the order specified by this value
3721
0
      int32_t tabIndex;
3722
0
      frame->IsFocusable(&tabIndex, 0);
3723
0
3724
0
      LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
3725
0
      LOGFOCUSNAVIGATION(("  with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
3726
0
3727
0
      if (tabIndex >= 0) {
3728
0
        NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
3729
0
        if (!aForDocumentNavigation &&
3730
0
            currentContent->IsHTMLElement(nsGkAtoms::img) &&
3731
0
            currentContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
3732
0
          // This is an image with a map. Image map areas are not traversed by
3733
0
          // nsIFrameTraversal so look for the next or previous area element.
3734
0
          nsIContent *areaContent =
3735
0
            GetNextTabbableMapArea(aForward, aCurrentTabIndex,
3736
0
                                   currentContent->AsElement(), iterStartContent);
3737
0
          if (areaContent) {
3738
0
            NS_ADDREF(*aResultContent = areaContent);
3739
0
            return NS_OK;
3740
0
          }
3741
0
        }
3742
0
        else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
3743
0
          // break out if we've wrapped around to the start again.
3744
0
          if (aOriginalStartContent && currentContent == aOriginalStartContent) {
3745
0
            NS_ADDREF(*aResultContent = currentContent);
3746
0
            return NS_OK;
3747
0
          }
3748
0
3749
0
          // If this is a remote child browser, call NavigateDocument to have
3750
0
          // the child process continue the navigation. Return a special error
3751
0
          // code to have the caller return early. If the child ends up not
3752
0
          // being focusable in some way, the child process will call back
3753
0
          // into document navigation again by calling MoveFocus.
3754
0
          TabParent* remote = TabParent::GetFrom(currentContent);
3755
0
          if (remote) {
3756
0
            remote->NavigateByKey(aForward, aForDocumentNavigation);
3757
0
            return NS_SUCCESS_DOM_NO_OPERATION;
3758
0
          }
3759
0
3760
0
          // Next, for document navigation, check if this a non-remote child document.
3761
0
          bool checkSubDocument = true;
3762
0
          if (aForDocumentNavigation &&
3763
0
              TryDocumentNavigation(currentContent, &checkSubDocument,
3764
0
                                    aResultContent)) {
3765
0
            return NS_OK;
3766
0
          }
3767
0
3768
0
          if (checkSubDocument) {
3769
0
            // found a node with a matching tab index. Check if it is a child
3770
0
            // frame. If so, navigate into the child frame instead.
3771
0
            if (TryToMoveFocusToSubDocument(currentContent,
3772
0
                                            aOriginalStartContent,
3773
0
                                            aForward, aForDocumentNavigation,
3774
0
                                            aResultContent)) {
3775
0
              MOZ_ASSERT(*aResultContent);
3776
0
              return NS_OK;
3777
0
            }
3778
0
            // otherwise, use this as the next content node to tab to, unless
3779
0
            // this was the element we started on. This would happen for
3780
0
            // instance on an element with child frames, where frame navigation
3781
0
            // could return the original element again. In that case, just skip
3782
0
            // it. Also, if the next content node is the root content, then
3783
0
            // return it. This latter case would happen only if someone made a
3784
0
            // popup focusable.
3785
0
            // Also, when going backwards, check to ensure that the focus
3786
0
            // wouldn't be redirected. Otherwise, for example, when an input in
3787
0
            // a textbox is focused, the enclosing textbox would be found and
3788
0
            // the same inner input would be returned again.
3789
0
            else if (currentContent == aRootContent ||
3790
0
                     (currentContent != startContent &&
3791
0
                      (aForward || !GetRedirectedFocus(currentContent)))) {
3792
0
3793
0
              if (nsDocument::IsShadowDOMEnabled(aRootContent)) {
3794
0
                // If currentContent is a shadow host in backward
3795
0
                // navigation, search in scope owned by currentContent
3796
0
                if (!aForward && currentContent->GetShadowRoot()) {
3797
0
                  nsIContent* contentToFocus =
3798
0
                    GetNextTabbableContentInScope(currentContent,
3799
0
                                                  currentContent,
3800
0
                                                  aOriginalStartContent,
3801
0
                                                  aForward, aForward ? 1 : 0,
3802
0
                                                  aIgnoreTabIndex,
3803
0
                                                  aForDocumentNavigation,
3804
0
                                                  true /* aSkipOwner */);
3805
0
                  if (contentToFocus) {
3806
0
                    NS_ADDREF(*aResultContent = contentToFocus);
3807
0
                    return NS_OK;
3808
0
                  }
3809
0
                }
3810
0
              }
3811
0
3812
0
              NS_ADDREF(*aResultContent = currentContent);
3813
0
              return NS_OK;
3814
0
            }
3815
0
          }
3816
0
        }
3817
0
      }
3818
0
      else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
3819
0
        // not focusable, so return if we have wrapped around to the original
3820
0
        // content. This is necessary in case the original starting content was
3821
0
        // not focusable.
3822
0
        NS_ADDREF(*aResultContent = currentContent);
3823
0
        return NS_OK;
3824
0
      }
3825
0
3826
0
      // Move to the next or previous frame, but ignore continuation frames
3827
0
      // since only the first frame should be involved in focusability.
3828
0
      // Otherwise, a loop will occur in the following example:
3829
0
      //   <span tabindex="1">...<a/><a/>...</span>
3830
0
      // where the text wraps onto multiple lines. Tabbing from the second
3831
0
      // link can find one of the span's continuation frames between the link
3832
0
      // and the end of the span, and the span would end up getting focused
3833
0
      // again.
3834
0
      do {
3835
0
        if (aForward)
3836
0
          frameTraversal->Next();
3837
0
        else
3838
0
          frameTraversal->Prev();
3839
0
        frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
3840
0
      } while (frame && frame->GetPrevContinuation());
3841
0
    }
3842
0
3843
0
    // If already at lowest priority tab (0), end search completely.
3844
0
    // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
3845
0
    if (aCurrentTabIndex == (aForward ? 0 : 1)) {
3846
0
      // if going backwards, the canvas should be focused once the beginning
3847
0
      // has been reached, so get the root element.
3848
0
      if (!aForward) {
3849
0
        nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent);
3850
0
        NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
3851
0
3852
0
        RefPtr<Element> docRoot =
3853
0
          GetRootForFocus(window, aRootContent->GetComposedDoc(), false, true);
3854
0
        FocusFirst(docRoot, aResultContent);
3855
0
      }
3856
0
      break;
3857
0
    }
3858
0
3859
0
    // continue looking for next highest priority tabindex
3860
0
    aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
3861
0
    startContent = iterStartContent = aRootContent;
3862
0
  }
3863
0
3864
0
  return NS_OK;
3865
0
}
3866
3867
bool
3868
nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent,
3869
                                      bool* aCheckSubDocument,
3870
                                      nsIContent** aResultContent)
3871
0
{
3872
0
  *aCheckSubDocument = true;
3873
0
  Element* docRoot = GetRootForChildDocument(aCurrentContent);
3874
0
  if (docRoot) {
3875
0
    // If GetRootForChildDocument returned something then call
3876
0
    // FocusFirst to find the root or first element to focus within
3877
0
    // the child document. If this is a frameset though, skip this and
3878
0
    // fall through to normal tab navigation to iterate into
3879
0
    // the frameset's frames and locate the first focusable frame.
3880
0
    if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
3881
0
      *aCheckSubDocument = false;
3882
0
      Unused << FocusFirst(docRoot, aResultContent);
3883
0
      return *aResultContent != nullptr;
3884
0
    }
3885
0
  } else {
3886
0
    // Set aCheckSubDocument to false, as this was neither a frame
3887
0
    // type element or a child document that was focusable.
3888
0
    *aCheckSubDocument = false;
3889
0
  }
3890
0
3891
0
  return false;
3892
0
}
3893
3894
bool
3895
nsFocusManager::TryToMoveFocusToSubDocument(nsIContent* aCurrentContent,
3896
                                            nsIContent* aOriginalStartContent,
3897
                                            bool aForward,
3898
                                            bool aForDocumentNavigation,
3899
                                            nsIContent** aResultContent)
3900
0
{
3901
0
  nsIDocument* doc = aCurrentContent->GetComposedDoc();
3902
0
  NS_ASSERTION(doc, "content not in document");
3903
0
  nsIDocument* subdoc = doc->GetSubDocumentFor(aCurrentContent);
3904
0
  if (subdoc && !subdoc->EventHandlingSuppressed()) {
3905
0
    if (aForward) {
3906
0
      // When tabbing forward into a frame, return the root
3907
0
      // frame so that the canvas becomes focused.
3908
0
      nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow();
3909
0
      if (subframe) {
3910
0
        *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
3911
0
        if (*aResultContent) {
3912
0
          NS_ADDREF(*aResultContent);
3913
0
          return true;
3914
0
        }
3915
0
      }
3916
0
    }
3917
0
    Element* rootElement = subdoc->GetRootElement();
3918
0
    nsIPresShell* subShell = subdoc->GetShell();
3919
0
    if (rootElement && subShell) {
3920
0
      nsresult rv = GetNextTabbableContent(subShell, rootElement,
3921
0
                                           aOriginalStartContent, rootElement,
3922
0
                                           aForward, (aForward ? 1 : 0),
3923
0
                                           false, aForDocumentNavigation,
3924
0
                                           aResultContent);
3925
0
      NS_ENSURE_SUCCESS(rv, false);
3926
0
      if (*aResultContent) {
3927
0
        return true;
3928
0
      }
3929
0
    }
3930
0
  }
3931
0
  return false;
3932
0
}
3933
3934
nsIContent*
3935
nsFocusManager::GetNextTabbableMapArea(bool aForward,
3936
                                       int32_t aCurrentTabIndex,
3937
                                       Element* aImageContent,
3938
                                       nsIContent* aStartContent)
3939
0
{
3940
0
  if (aImageContent->IsInComposedDoc()) {
3941
0
    HTMLImageElement* imgElement = HTMLImageElement::FromNode(aImageContent);
3942
0
    // The caller should check the element type, so we can assert here.
3943
0
    MOZ_ASSERT(imgElement);
3944
0
3945
0
    nsCOMPtr<nsIContent> mapContent = imgElement->FindImageMap();
3946
0
    if (!mapContent)
3947
0
      return nullptr;
3948
0
    uint32_t count = mapContent->GetChildCount();
3949
0
    // First see if the the start content is in this map
3950
0
3951
0
    int32_t index = mapContent->ComputeIndexOf(aStartContent);
3952
0
    int32_t tabIndex;
3953
0
    if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
3954
0
                      tabIndex != aCurrentTabIndex)) {
3955
0
      // If aStartContent is in this map we must start iterating past it.
3956
0
      // We skip the case where aStartContent has tabindex == aStartContent
3957
0
      // since the next tab ordered element might be before it
3958
0
      // (or after for backwards) in the child list.
3959
0
      index = aForward ? -1 : (int32_t)count;
3960
0
    }
3961
0
3962
0
    // GetChildAt_Deprecated will return nullptr if our index < 0 or index >=
3963
0
    // count
3964
0
    nsCOMPtr<nsIContent> areaContent;
3965
0
    while ((areaContent = mapContent->GetChildAt_Deprecated(aForward ? ++index : --index)) != nullptr) {
3966
0
      if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
3967
0
        return areaContent;
3968
0
      }
3969
0
    }
3970
0
  }
3971
0
3972
0
  return nullptr;
3973
0
}
3974
3975
int32_t
3976
nsFocusManager::GetNextTabIndex(nsIContent* aParent,
3977
                                int32_t aCurrentTabIndex,
3978
                                bool aForward)
3979
0
{
3980
0
  int32_t tabIndex, childTabIndex;
3981
0
  FlattenedChildIterator iter(aParent);
3982
0
3983
0
  if (aForward) {
3984
0
    tabIndex = 0;
3985
0
    for (nsIContent* child = iter.GetNextChild();
3986
0
         child;
3987
0
         child = iter.GetNextChild()) {
3988
0
      // Skip child's descendants if child is a shadow host or slot, as they are
3989
0
      // in the focus navigation scope owned by child's shadow root
3990
0
      if (!(nsDocument::IsShadowDOMEnabled(aParent) && IsHostOrSlot(child))) {
3991
0
        childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
3992
0
        if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
3993
0
          tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
3994
0
        }
3995
0
      }
3996
0
3997
0
      nsAutoString tabIndexStr;
3998
0
      if (child->IsElement()) {
3999
0
        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
4000
0
      }
4001
0
      nsresult ec;
4002
0
      int32_t val = tabIndexStr.ToInteger(&ec);
4003
0
      if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
4004
0
        tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
4005
0
      }
4006
0
    }
4007
0
  }
4008
0
  else { /* !aForward */
4009
0
    tabIndex = 1;
4010
0
    for (nsIContent* child = iter.GetNextChild();
4011
0
         child;
4012
0
         child = iter.GetNextChild()) {
4013
0
      // Skip child's descendants if child is a shadow host or slot, as they are
4014
0
      // in the focus navigation scope owned by child's shadow root
4015
0
      if (!(nsDocument::IsShadowDOMEnabled(aParent) && IsHostOrSlot(child))) {
4016
0
        childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
4017
0
        if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
4018
0
            (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
4019
0
          tabIndex = childTabIndex;
4020
0
        }
4021
0
      }
4022
0
4023
0
      nsAutoString tabIndexStr;
4024
0
      if (child->IsElement()) {
4025
0
        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
4026
0
      }
4027
0
      nsresult ec;
4028
0
      int32_t val = tabIndexStr.ToInteger(&ec);
4029
0
      if (NS_SUCCEEDED (ec)) {
4030
0
        if ((aCurrentTabIndex == 0 && val > tabIndex) ||
4031
0
            (val < aCurrentTabIndex && val > tabIndex) ) {
4032
0
          tabIndex = val;
4033
0
        }
4034
0
      }
4035
0
    }
4036
0
  }
4037
0
4038
0
  return tabIndex;
4039
0
}
4040
4041
nsresult
4042
nsFocusManager::FocusFirst(Element* aRootElement, nsIContent** aNextContent)
4043
0
{
4044
0
  if (!aRootElement) {
4045
0
    return NS_OK;
4046
0
  }
4047
0
4048
0
  nsIDocument* doc = aRootElement->GetComposedDoc();
4049
0
  if (doc) {
4050
0
    if (doc->IsXULDocument()) {
4051
0
      // If the redirectdocumentfocus attribute is set, redirect the focus to a
4052
0
      // specific element. This is primarily used to retarget the focus to the
4053
0
      // urlbar during document navigation.
4054
0
      nsAutoString retarget;
4055
0
4056
0
      if (aRootElement->GetAttr(kNameSpaceID_None,
4057
0
                               nsGkAtoms::retargetdocumentfocus, retarget)) {
4058
0
        nsCOMPtr<Element> element = doc->GetElementById(retarget);
4059
0
        nsCOMPtr<nsIContent> retargetElement =
4060
0
          CheckIfFocusable(element, 0);
4061
0
        if (retargetElement) {
4062
0
          retargetElement.forget(aNextContent);
4063
0
          return NS_OK;
4064
0
        }
4065
0
      }
4066
0
    }
4067
0
4068
0
    nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
4069
0
    if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
4070
0
      // If the found content is in a chrome shell, navigate forward one
4071
0
      // tabbable item so that the first item is focused. Note that we
4072
0
      // always go forward and not back here.
4073
0
      nsIPresShell* presShell = doc->GetShell();
4074
0
      if (presShell) {
4075
0
        return GetNextTabbableContent(presShell, aRootElement,
4076
0
                                      nullptr, aRootElement,
4077
0
                                      true, 1, false, false,
4078
0
                                      aNextContent);
4079
0
      }
4080
0
    }
4081
0
  }
4082
0
4083
0
  NS_ADDREF(*aNextContent = aRootElement);
4084
0
  return NS_OK;
4085
0
}
4086
4087
Element*
4088
nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow,
4089
                                nsIDocument* aDocument,
4090
                                bool aForDocumentNavigation,
4091
                                bool aCheckVisibility)
4092
0
{
4093
0
  if (!aForDocumentNavigation) {
4094
0
    nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
4095
0
    if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
4096
0
      return nullptr;
4097
0
    }
4098
0
  }
4099
0
4100
0
  if (aCheckVisibility && !IsWindowVisible(aWindow))
4101
0
    return nullptr;
4102
0
4103
0
  // If the body is contenteditable, use the editor's root element rather than
4104
0
  // the actual root element.
4105
0
  RefPtr<Element> rootElement =
4106
0
    nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
4107
0
  if (!rootElement || !rootElement->GetPrimaryFrame()) {
4108
0
    rootElement = aDocument->GetRootElement();
4109
0
    if (!rootElement) {
4110
0
      return nullptr;
4111
0
    }
4112
0
  }
4113
0
4114
0
  if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
4115
0
    return nullptr;
4116
0
  }
4117
0
4118
0
  // Finally, check if this is a frameset
4119
0
  nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
4120
0
  if (htmlDoc) {
4121
0
    Element* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
4122
0
    if (htmlChild) {
4123
0
      // In document navigation mode, return the frameset so that navigation
4124
0
      // descends into the child frames.
4125
0
      return aForDocumentNavigation ? htmlChild : nullptr;
4126
0
    }
4127
0
  }
4128
0
4129
0
  return rootElement;
4130
0
}
4131
4132
Element*
4133
nsFocusManager::GetRootForChildDocument(nsIContent* aContent)
4134
0
{
4135
0
  // Check for elements that represent child documents, that is, browsers,
4136
0
  // editors or frames from a frameset. We don't include iframes since we
4137
0
  // consider them to be an integral part of the same window or page.
4138
0
  if (!aContent ||
4139
0
      !(aContent->IsXULElement(nsGkAtoms::browser) ||
4140
0
        aContent->IsXULElement(nsGkAtoms::editor) ||
4141
0
        aContent->IsHTMLElement(nsGkAtoms::frame))) {
4142
0
    return nullptr;
4143
0
  }
4144
0
4145
0
  nsIDocument* doc = aContent->GetComposedDoc();
4146
0
  if (!doc) {
4147
0
    return nullptr;
4148
0
  }
4149
0
4150
0
  nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
4151
0
  if (!subdoc || subdoc->EventHandlingSuppressed()) {
4152
0
    return nullptr;
4153
0
  }
4154
0
4155
0
  nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow();
4156
0
  return GetRootForFocus(window, subdoc, true, true);
4157
0
}
4158
4159
void
4160
nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow,
4161
                                    nsIContent* aStartSelection,
4162
                                    nsIContent* aEndSelection,
4163
                                    nsIContent** aFocusedContent)
4164
0
{
4165
0
  *aFocusedContent = nullptr;
4166
0
4167
0
  nsCOMPtr<nsIContent> testContent = aStartSelection;
4168
0
  nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
4169
0
4170
0
  nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedElement();
4171
0
4172
0
  // We now have the correct start node in selectionContent!
4173
0
  // Search for focusable elements, starting with selectionContent
4174
0
4175
0
  // Method #1: Keep going up while we look - an ancestor might be focusable
4176
0
  // We could end the loop earlier, such as when we're no longer
4177
0
  // in the same frame, by comparing selectionContent->GetPrimaryFrame()
4178
0
  // with a variable holding the starting selectionContent
4179
0
  while (testContent) {
4180
0
    // Keep testing while selectionContent is equal to something,
4181
0
    // eventually we'll run out of ancestors
4182
0
4183
0
    nsCOMPtr<nsIURI> uri;
4184
0
    if (testContent == currentFocus ||
4185
0
        testContent->IsLink(getter_AddRefs(uri))) {
4186
0
      testContent.forget(aFocusedContent);
4187
0
      return;
4188
0
    }
4189
0
4190
0
    // Get the parent
4191
0
    testContent = testContent->GetParent();
4192
0
4193
0
    if (!testContent) {
4194
0
      // We run this loop again, checking the ancestor chain of the selection's end point
4195
0
      testContent = nextTestContent;
4196
0
      nextTestContent = nullptr;
4197
0
    }
4198
0
  }
4199
0
4200
0
  // We couldn't find an anchor that was an ancestor of the selection start
4201
0
  // Method #2: look for anchor in selection's primary range (depth first search)
4202
0
4203
0
  nsCOMPtr<nsIContent> selectionNode = aStartSelection;
4204
0
  nsCOMPtr<nsIContent> endSelectionNode = aEndSelection;
4205
0
  nsCOMPtr<nsIContent> testNode;
4206
0
4207
0
  do {
4208
0
    testContent = selectionNode;
4209
0
4210
0
    // We're looking for any focusable link that could be part of the
4211
0
    // main document's selection.
4212
0
    nsCOMPtr<nsIURI> uri;
4213
0
    if (testContent == currentFocus ||
4214
0
        testContent->IsLink(getter_AddRefs(uri))) {
4215
0
      testContent.forget(aFocusedContent);
4216
0
      return;
4217
0
    }
4218
0
4219
0
    nsIContent* testNode = selectionNode->GetFirstChild();
4220
0
    if (testNode) {
4221
0
      selectionNode = testNode;
4222
0
      continue;
4223
0
    }
4224
0
4225
0
    if (selectionNode == endSelectionNode)
4226
0
      break;
4227
0
    testNode = selectionNode->GetNextSibling();
4228
0
    if (testNode) {
4229
0
      selectionNode = testNode;
4230
0
      continue;
4231
0
    }
4232
0
4233
0
    do {
4234
0
      // GetParent is OK here, instead of GetParentNode, because the only case
4235
0
      // where the latter returns something different from the former is when
4236
0
      // GetParentNode is the document.  But in that case we would simply get
4237
0
      // null for selectionNode when setting it to testNode->GetNextSibling()
4238
0
      // (because a document has no next sibling).  And then the next iteration
4239
0
      // of this loop would get null for GetParentNode anyway, and break out of
4240
0
      // all the loops.
4241
0
      testNode = selectionNode->GetParent();
4242
0
      if (!testNode || testNode == endSelectionNode) {
4243
0
        selectionNode = nullptr;
4244
0
        break;
4245
0
      }
4246
0
      selectionNode = testNode->GetNextSibling();
4247
0
      if (selectionNode)
4248
0
        break;
4249
0
      selectionNode = testNode;
4250
0
    } while (true);
4251
0
  }
4252
0
  while (selectionNode && selectionNode != endSelectionNode);
4253
0
}
4254
4255
class PointerUnlocker : public Runnable
4256
{
4257
public:
4258
  PointerUnlocker()
4259
    : mozilla::Runnable("PointerUnlocker")
4260
0
  {
4261
0
    MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
4262
0
    PointerUnlocker::sActiveUnlocker = this;
4263
0
  }
4264
4265
  ~PointerUnlocker()
4266
0
  {
4267
0
    if (PointerUnlocker::sActiveUnlocker == this) {
4268
0
      PointerUnlocker::sActiveUnlocker = nullptr;
4269
0
    }
4270
0
  }
4271
4272
  NS_IMETHOD Run() override
4273
0
  {
4274
0
    if (PointerUnlocker::sActiveUnlocker == this) {
4275
0
      PointerUnlocker::sActiveUnlocker = nullptr;
4276
0
    }
4277
0
    NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
4278
0
    nsPIDOMWindowOuter* focused =
4279
0
      nsFocusManager::GetFocusManager()->GetFocusedWindow();
4280
0
    nsCOMPtr<nsIDocument> pointerLockedDoc =
4281
0
      do_QueryReferent(EventStateManager::sPointerLockedDoc);
4282
0
    if (pointerLockedDoc &&
4283
0
        !nsContentUtils::IsInPointerLockContext(focused)) {
4284
0
      nsIDocument::UnlockPointer();
4285
0
    }
4286
0
    return NS_OK;
4287
0
  }
4288
4289
  static PointerUnlocker* sActiveUnlocker;
4290
};
4291
4292
PointerUnlocker*
4293
PointerUnlocker::sActiveUnlocker = nullptr;
4294
4295
void
4296
nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow)
4297
0
{
4298
0
  if (!PointerUnlocker::sActiveUnlocker &&
4299
0
      nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
4300
0
      !nsContentUtils::IsInPointerLockContext(aWindow)) {
4301
0
    nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
4302
0
    NS_DispatchToCurrentThread(runnable);
4303
0
  }
4304
0
4305
0
  // Update the last focus time on any affected documents
4306
0
  if (aWindow && aWindow != mFocusedWindow) {
4307
0
    const TimeStamp now(TimeStamp::Now());
4308
0
    for (nsIDocument* doc = aWindow->GetExtantDoc();
4309
0
         doc;
4310
0
         doc = doc->GetParentDocument()) {
4311
0
      doc->SetLastFocusTime(now);
4312
0
    }
4313
0
  }
4314
0
4315
0
  mFocusedWindow = aWindow;
4316
0
}
4317
4318
void
4319
nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration)
4320
0
{
4321
0
  if (!sInstance) {
4322
0
    return;
4323
0
  }
4324
0
4325
0
  if (sInstance->mActiveWindow) {
4326
0
    sInstance->mActiveWindow->
4327
0
      MarkUncollectableForCCGeneration(aGeneration);
4328
0
  }
4329
0
  if (sInstance->mFocusedWindow) {
4330
0
    sInstance->mFocusedWindow->
4331
0
      MarkUncollectableForCCGeneration(aGeneration);
4332
0
  }
4333
0
  if (sInstance->mWindowBeingLowered) {
4334
0
    sInstance->mWindowBeingLowered->
4335
0
      MarkUncollectableForCCGeneration(aGeneration);
4336
0
  }
4337
0
  if (sInstance->mFocusedElement) {
4338
0
    sInstance->mFocusedElement->OwnerDoc()->
4339
0
      MarkUncollectableForCCGeneration(aGeneration);
4340
0
  }
4341
0
  if (sInstance->mFirstBlurEvent) {
4342
0
    sInstance->mFirstBlurEvent->OwnerDoc()->
4343
0
      MarkUncollectableForCCGeneration(aGeneration);
4344
0
  }
4345
0
  if (sInstance->mFirstFocusEvent) {
4346
0
    sInstance->mFirstFocusEvent->OwnerDoc()->
4347
0
      MarkUncollectableForCCGeneration(aGeneration);
4348
0
  }
4349
0
  if (sInstance->mMouseButtonEventHandlingDocument) {
4350
0
    sInstance->mMouseButtonEventHandlingDocument->
4351
0
      MarkUncollectableForCCGeneration(aGeneration);
4352
0
  }
4353
0
}
4354
4355
bool
4356
nsFocusManager::CanSkipFocus(nsIContent* aContent)
4357
0
{
4358
0
  if (!aContent ||
4359
0
      nsContentUtils::IsChromeDoc(aContent->OwnerDoc())) {
4360
0
    return false;
4361
0
  }
4362
0
4363
0
  if (mFocusedElement == aContent) {
4364
0
    return true;
4365
0
  }
4366
0
4367
0
  nsIDocShell* ds = aContent->OwnerDoc()->GetDocShell();
4368
0
  if (!ds) {
4369
0
    return true;
4370
0
  }
4371
0
4372
0
  nsCOMPtr<nsIDocShellTreeItem> root;
4373
0
  ds->GetRootTreeItem(getter_AddRefs(root));
4374
0
  nsCOMPtr<nsPIDOMWindowOuter> newRootWindow =
4375
0
    root ? root->GetWindow() : nullptr;
4376
0
  if (mActiveWindow != newRootWindow) {
4377
0
    nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
4378
0
    if (outerWindow && outerWindow->GetFocusedElement() == aContent) {
4379
0
      return true;
4380
0
    }
4381
0
  }
4382
0
4383
0
  return false;
4384
0
}
4385
4386
nsresult
4387
NS_NewFocusManager(nsIFocusManager** aResult)
4388
0
{
4389
0
  NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
4390
0
  return NS_OK;
4391
0
}