summaryrefslogtreecommitdiff
path: root/dom/base/nsFocusManager.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/base/nsFocusManager.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/base/nsFocusManager.cpp')
-rw-r--r--dom/base/nsFocusManager.cpp3659
1 files changed, 3659 insertions, 0 deletions
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
new file mode 100644
index 0000000000..fb350fa120
--- /dev/null
+++ b/dom/base/nsFocusManager.cpp
@@ -0,0 +1,3659 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TabParent.h"
+
+#include "nsFocusManager.h"
+
+#include "AccessibleCaretEventHub.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIEditor.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMRange.h"
+#include "nsIHTMLDocument.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIFormControl.h"
+#include "nsLayoutUtils.h"
+#include "nsIPresShell.h"
+#include "nsFrameTraversal.h"
+#include "nsIWebNavigation.h"
+#include "nsCaret.h"
+#include "nsIBaseWindow.h"
+#include "nsIXULWindow.h"
+#include "nsViewManager.h"
+#include "nsFrameSelection.h"
+#include "mozilla/dom/Selection.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIPrincipal.h"
+#include "nsIObserverService.h"
+#include "nsIObjectFrame.h"
+#include "nsBindingManager.h"
+#include "nsStyleCoord.h"
+#include "TabChild.h"
+#include "nsFrameLoader.h"
+#include "nsNumberControlFrame.h"
+
+#include "mozilla/ContentEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include <algorithm>
+
+#ifdef MOZ_XUL
+#include "nsIDOMXULTextboxElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#endif
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+#ifndef XP_MACOSX
+#include "nsIScriptError.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::widget;
+
+// Two types of focus pr logging are available:
+// 'Focus' for normal focus manager calls
+// 'FocusNavigation' for tab and document navigation
+LazyLogModule gFocusLog("Focus");
+LazyLogModule gFocusNavigationLog("FocusNavigation");
+
+#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args)
+#define LOGFOCUSNAVIGATION(args) MOZ_LOG(gFocusNavigationLog, mozilla::LogLevel::Debug, args)
+
+#define LOGTAG(log, format, content) \
+ if (MOZ_LOG_TEST(log, LogLevel::Debug)) { \
+ nsAutoCString tag(NS_LITERAL_CSTRING("(none)")); \
+ if (content) { \
+ content->NodeInfo()->NameAtom()->ToUTF8String(tag); \
+ } \
+ MOZ_LOG(log, LogLevel::Debug, (format, tag.get())); \
+ }
+
+#define LOGCONTENT(format, content) LOGTAG(gFocusLog, format, content)
+#define LOGCONTENTNAVIGATION(format, content) LOGTAG(gFocusNavigationLog, format, content)
+
+struct nsDelayedBlurOrFocusEvent
+{
+ nsDelayedBlurOrFocusEvent(EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsIDocument* aDocument,
+ EventTarget* aTarget,
+ EventTarget* aRelatedTarget)
+ : mPresShell(aPresShell)
+ , mDocument(aDocument)
+ , mTarget(aTarget)
+ , mEventMessage(aEventMessage)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ nsDelayedBlurOrFocusEvent(const nsDelayedBlurOrFocusEvent& aOther)
+ : mPresShell(aOther.mPresShell)
+ , mDocument(aOther.mDocument)
+ , mTarget(aOther.mTarget)
+ , mEventMessage(aOther.mEventMessage)
+ {
+ }
+
+ nsCOMPtr<nsIPresShell> mPresShell;
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<EventTarget> mTarget;
+ EventMessage mEventMessage;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+inline void ImplCycleCollectionUnlink(nsDelayedBlurOrFocusEvent& aField)
+{
+ aField.mPresShell = nullptr;
+ aField.mDocument = nullptr;
+ aField.mTarget = nullptr;
+ aField.mRelatedTarget = nullptr;
+}
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsDelayedBlurOrFocusEvent& aField,
+ const char* aName,
+ uint32_t aFlags = 0)
+{
+ CycleCollectionNoteChild(aCallback, aField.mPresShell.get(), aName, aFlags);
+ CycleCollectionNoteChild(aCallback, aField.mDocument.get(), aName, aFlags);
+ CycleCollectionNoteChild(aCallback, aField.mTarget.get(), aName, aFlags);
+ CycleCollectionNoteChild(aCallback, aField.mRelatedTarget.get(), aName, aFlags);
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFocusManager)
+ NS_INTERFACE_MAP_ENTRY(nsIFocusManager)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFocusManager)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFocusManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFocusManager)
+
+NS_IMPL_CYCLE_COLLECTION(nsFocusManager,
+ mActiveWindow,
+ mFocusedWindow,
+ mFocusedContent,
+ mFirstBlurEvent,
+ mFirstFocusEvent,
+ mWindowBeingLowered,
+ mDelayedBlurFocusEvents,
+ mMouseButtonEventHandlingDocument)
+
+nsFocusManager* nsFocusManager::sInstance = nullptr;
+bool nsFocusManager::sMouseFocusesFormControl = false;
+bool nsFocusManager::sTestMode = false;
+
+static const char* kObservedPrefs[] = {
+ "accessibility.browsewithcaret",
+ "accessibility.tabfocus_applies_to_xul",
+ "accessibility.mouse_focuses_formcontrol",
+ "focusmanager.testmode",
+ nullptr
+};
+
+nsFocusManager::nsFocusManager()
+{ }
+
+nsFocusManager::~nsFocusManager()
+{
+ Preferences::RemoveObservers(this, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+}
+
+// static
+nsresult
+nsFocusManager::Init()
+{
+ nsFocusManager* fm = new nsFocusManager();
+ NS_ADDREF(fm);
+ sInstance = fm;
+
+ nsIContent::sTabFocusModelAppliesToXUL =
+ Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
+ nsIContent::sTabFocusModelAppliesToXUL);
+
+ sMouseFocusesFormControl =
+ Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
+
+ sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+
+ Preferences::AddWeakObservers(fm, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(fm, "xpcom-shutdown", true);
+ }
+
+ return NS_OK;
+}
+
+// static
+void
+nsFocusManager::Shutdown()
+{
+ NS_IF_RELEASE(sInstance);
+}
+
+NS_IMETHODIMP
+nsFocusManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsDependentString data(aData);
+ if (data.EqualsLiteral("accessibility.browsewithcaret")) {
+ UpdateCaretForCaretBrowsingMode();
+ }
+ else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
+ nsIContent::sTabFocusModelAppliesToXUL =
+ Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
+ nsIContent::sTabFocusModelAppliesToXUL);
+ }
+ else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
+ sMouseFocusesFormControl =
+ Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
+ false);
+ }
+ else if (data.EqualsLiteral("focusmanager.testmode")) {
+ sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+ }
+ } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ mActiveWindow = nullptr;
+ mFocusedWindow = nullptr;
+ mFocusedContent = nullptr;
+ mFirstBlurEvent = nullptr;
+ mFirstFocusEvent = nullptr;
+ mWindowBeingLowered = nullptr;
+ mDelayedBlurFocusEvents.Clear();
+ mMouseButtonEventHandlingDocument = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// given a frame content node, retrieve the nsIDOMWindow displayed in it
+static nsPIDOMWindowOuter*
+GetContentWindow(nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (doc) {
+ nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+ if (subdoc)
+ return subdoc->GetWindow();
+ }
+
+ return nullptr;
+}
+
+// get the current window for the given content node
+static nsPIDOMWindowOuter*
+GetCurrentWindow(nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ return doc ? doc->GetWindow() : nullptr;
+}
+
+// static
+nsIContent*
+nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep,
+ nsPIDOMWindowOuter** aFocusedWindow)
+{
+ NS_ENSURE_TRUE(aWindow, nullptr);
+
+ *aFocusedWindow = nullptr;
+
+ nsIContent* currentContent = nullptr;
+ nsPIDOMWindowOuter* window = aWindow;
+ while (window) {
+ *aFocusedWindow = window;
+ currentContent = window->GetFocusedNode();
+ if (!currentContent || !aDeep)
+ break;
+
+ window = GetContentWindow(currentContent);
+ }
+
+ NS_IF_ADDREF(*aFocusedWindow);
+
+ return currentContent;
+}
+
+// static
+nsIContent*
+nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
+{
+ // For input number, redirect focus to our anonymous text control.
+ if (aContent->IsHTMLElement(nsGkAtoms::input)) {
+ bool typeIsNumber =
+ static_cast<dom::HTMLInputElement*>(aContent)->GetType() ==
+ NS_FORM_INPUT_NUMBER;
+
+ if (typeIsNumber) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(aContent->GetPrimaryFrame());
+
+ if (numberControlFrame) {
+ HTMLInputElement* textControl =
+ numberControlFrame->GetAnonTextControl();
+ return static_cast<nsIContent*>(textControl);
+ }
+ }
+ }
+
+#ifdef MOZ_XUL
+ if (aContent->IsXULElement()) {
+ nsCOMPtr<nsIDOMNode> inputField;
+
+ nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
+ if (textbox) {
+ textbox->GetInputField(getter_AddRefs(inputField));
+ }
+ else {
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
+ if (menulist) {
+ menulist->GetInputField(getter_AddRefs(inputField));
+ }
+ else if (aContent->IsXULElement(nsGkAtoms::scale)) {
+ nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
+ if (!doc)
+ return nullptr;
+
+ nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent);
+ if (children) {
+ nsIContent* child = children->Item(0);
+ if (child && child->IsXULElement(nsGkAtoms::slider))
+ return child;
+ }
+ }
+ }
+
+ if (inputField) {
+ nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField);
+ return retval;
+ }
+ }
+#endif
+
+ return nullptr;
+}
+
+// static
+InputContextAction::Cause
+nsFocusManager::GetFocusMoveActionCause(uint32_t aFlags)
+{
+ if (aFlags & nsIFocusManager::FLAG_BYTOUCH) {
+ return InputContextAction::CAUSE_TOUCH;
+ } else if (aFlags & nsIFocusManager::FLAG_BYMOUSE) {
+ return InputContextAction::CAUSE_MOUSE;
+ } else if (aFlags & nsIFocusManager::FLAG_BYKEY) {
+ return InputContextAction::CAUSE_KEY;
+ }
+ return InputContextAction::CAUSE_UNKNOWN;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow)
+{
+ NS_IF_ADDREF(*aWindow = mActiveWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::SetActiveWindow(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_STATE(aWindow);
+
+ // only top-level windows can be made active
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(piWindow == piWindow->GetPrivateRoot(), NS_ERROR_INVALID_ARG);
+
+ RaiseWindow(piWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow)
+{
+ NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFocusManager::SetFocusedWindow(mozIDOMWindowProxy* aWindowToFocus)
+{
+ LOGFOCUS(("<<SetFocusedWindow begin>>"));
+
+ nsCOMPtr<nsPIDOMWindowOuter> windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus);
+ NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
+
+ windowToFocus = windowToFocus->GetOuterWindow();
+
+ nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
+ if (frameElement) {
+ // pass false for aFocusChanged so that the caret does not get updated
+ // and scrolling does not occur.
+ SetFocusInner(frameElement, 0, false, true);
+ }
+ else {
+ // this is a top-level window. If the window has a child frame focused,
+ // clear the focus. Otherwise, focus should already be in this frame, or
+ // already cleared. This ensures that focus will be in this frame and not
+ // in a child.
+ nsIContent* content = windowToFocus->GetFocusedNode();
+ if (content) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
+ ClearFocus(windowToFocus);
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
+ if (rootWindow)
+ RaiseWindow(rootWindow);
+
+ LOGFOCUS(("<<SetFocusedWindow end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement)
+{
+ if (mFocusedContent)
+ CallQueryInterface(mFocusedContent, aFocusedElement);
+ else
+ *aFocusedElement = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow, uint32_t* aLastFocusMethod)
+{
+ // the focus method is stored on the inner window
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (aWindow) {
+ window = nsPIDOMWindowOuter::From(aWindow);
+ }
+ if (!window)
+ window = mFocusedWindow;
+
+ *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
+
+ NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
+ "invalid focus method");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::SetFocus(nsIDOMElement* aElement, uint32_t aFlags)
+{
+ LOGFOCUS(("<<SetFocus begin>>"));
+
+ nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
+ NS_ENSURE_ARG(newFocus);
+
+ SetFocusInner(newFocus, aFlags, true, true);
+
+ LOGFOCUS(("<<SetFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags,
+ bool* aIsFocusable)
+{
+ NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
+
+ nsCOMPtr<nsIContent> aContent = do_QueryInterface(aElement);
+
+ *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, nsIDOMElement* aStartElement,
+ uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+
+ LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
+ nsIDocument* doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %p %s",
+ mFocusedWindow.get(),
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+
+ LOGCONTENT(" Current Focus: %s", mFocusedContent.get());
+
+ // use FLAG_BYMOVEFOCUS when switching focus with MoveFocus unless one of
+ // the other focus methods is already set, or we're just moving to the root
+ // or caret position.
+ if (aType != MOVEFOCUS_ROOT && aType != MOVEFOCUS_CARET &&
+ (aFlags & FOCUSMETHOD_MASK) == 0) {
+ aFlags |= FLAG_BYMOVEFOCUS;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ nsCOMPtr<nsIContent> startContent;
+ if (aStartElement) {
+ startContent = do_QueryInterface(aStartElement);
+ NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
+
+ window = GetCurrentWindow(startContent);
+ }
+ else {
+ window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+ }
+
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
+ nsCOMPtr<nsIContent> newFocus;
+ nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
+ getter_AddRefs(newFocus));
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
+
+ if (newFocus) {
+ // for caret movement, pass false for the aFocusChanged argument,
+ // otherwise the caret will end up moving to the focus position. This
+ // would be a problem because the caret would move to the beginning of the
+ // focused link making it impossible to navigate the caret over a link.
+ SetFocusInner(newFocus, aFlags, aType != MOVEFOCUS_CARET, true);
+ CallQueryInterface(newFocus, aElement);
+ }
+ else if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_CARET) {
+ // no content was found, so clear the focus for these two types.
+ ClearFocus(window);
+ }
+
+ LOGFOCUS(("<<MoveFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ClearFocus(mozIDOMWindowProxy* aWindow)
+{
+ LOGFOCUS(("<<ClearFocus begin>>"));
+
+ // if the window to clear is the focused window or an ancestor of the
+ // focused window, then blur the existing focused content. Otherwise, the
+ // focus is somewhere else so just update the current node.
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (IsSameOrAncestor(window, mFocusedWindow)) {
+ bool isAncestor = (window != mFocusedWindow);
+ if (Blur(window, nullptr, isAncestor, true)) {
+ // if we are clearing the focus on an ancestor of the focused window,
+ // the ancestor will become the new focused window, so focus it
+ if (isAncestor)
+ Focus(window, nullptr, 0, true, false, false, true);
+ }
+ }
+ else {
+ window->SetFocusedNode(nullptr);
+ }
+
+ LOGFOCUS(("<<ClearFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
+ bool aDeep,
+ mozIDOMWindowProxy** aFocusedWindow,
+ nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+ if (aFocusedWindow)
+ *aFocusedWindow = nullptr;
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsCOMPtr<nsIContent> focusedContent =
+ GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
+ if (focusedContent)
+ CallQueryInterface(focusedContent, aElement);
+
+ if (aFocusedWindow)
+ NS_IF_ADDREF(*aFocusedWindow = focusedWindow);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::MoveCaretToFocus(mozIDOMWindowProxy* aWindow)
+{
+ nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(aWindow);
+ nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(webnav);
+ if (dsti) {
+ if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(dsti);
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ // don't move the caret for editable documents
+ bool isEditable;
+ docShell->GetEditable(&isEditable);
+ if (isEditable)
+ return NS_OK;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ nsCOMPtr<nsIContent> content = window->GetFocusedNode();
+ if (content)
+ MoveCaretToFocus(presShell, content);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Raised Window: %p %s", aWindow,
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ if (mActiveWindow) {
+ doc = mActiveWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Active Window: %p %s", mActiveWindow.get(),
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (mActiveWindow == window) {
+ // The window is already active, so there is no need to focus anything,
+ // but make sure that the right widget is focused. This is a special case
+ // for Windows because when restoring a minimized window, a second
+ // activation will occur and the top-level widget could be focused instead
+ // of the child we want. We solve this by calling SetFocus to ensure that
+ // what the focus manager thinks should be the current widget is actually
+ // focused.
+ EnsureCurrentWidgetFocused();
+ return NS_OK;
+ }
+
+ // lower the existing window, if any. This shouldn't happen usually.
+ if (mActiveWindow)
+ WindowLowered(mActiveWindow);
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
+ // If there's no docShellAsItem, this window must have been closed,
+ // in that case there is no tree owner.
+ NS_ENSURE_TRUE(docShellAsItem, NS_OK);
+
+ // set this as the active window
+ mActiveWindow = window;
+
+ // ensure that the window is enabled and visible
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
+ if (baseWindow) {
+ bool isEnabled = true;
+ if (NS_SUCCEEDED(baseWindow->GetEnabled(&isEnabled)) && !isEnabled) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!sTestMode) {
+ baseWindow->SetVisibility(true);
+ }
+ }
+
+ // If this is a parent or single process window, send the activate event.
+ // Events for child process windows will be sent when ParentActivated
+ // is called.
+ if (XRE_IsParentProcess()) {
+ ActivateOrDeactivate(window, true);
+ }
+
+ // retrieve the last focused element within the window that was raised
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
+ nsCOMPtr<nsIContent> currentFocus =
+ GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
+
+ NS_ASSERTION(currentWindow, "window raised with no window current");
+ if (!currentWindow)
+ return NS_OK;
+
+ // If there is no nsIXULWindow, then this is an embedded or child process window.
+ // Pass false for aWindowRaised so that commands get updated.
+ nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(baseWindow));
+ Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Lowered [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Lowered Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ if (mActiveWindow) {
+ doc = mActiveWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Active Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (mActiveWindow != window)
+ return NS_OK;
+
+ // clear the mouse capture as the active window has changed
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+
+ // In addition, reset the drag state to ensure that we are no longer in
+ // drag-select mode.
+ if (mFocusedWindow) {
+ nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (presShell) {
+ RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
+ frameSelection->SetDragState(false);
+ }
+ }
+ }
+
+ // If this is a parent or single process window, send the deactivate event.
+ // Events for child process windows will be sent when ParentActivated
+ // is called.
+ if (XRE_IsParentProcess()) {
+ ActivateOrDeactivate(window, false);
+ }
+
+ // keep track of the window being lowered, so that attempts to raise the
+ // window can be prevented until we return. Otherwise, focus can get into
+ // an unusual state.
+ mWindowBeingLowered = mActiveWindow;
+ mActiveWindow = nullptr;
+
+ if (mFocusedWindow)
+ Blur(nullptr, nullptr, true, true);
+
+ mWindowBeingLowered = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsFocusManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
+{
+ NS_ENSURE_ARG(aDocument);
+ NS_ENSURE_ARG(aContent);
+
+ nsPIDOMWindowOuter *window = aDocument->GetWindow();
+ if (!window)
+ return NS_OK;
+
+ // if the content is currently focused in the window, or is an ancestor
+ // of the currently focused element, reset the focus within that window.
+ nsIContent* content = window->GetFocusedNode();
+ if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
+ bool shouldShowFocusRing = window->ShouldShowFocusRing();
+ window->SetFocusedNode(nullptr);
+
+ // if this window is currently focused, clear the global focused
+ // element as well, but don't fire any events.
+ if (window == mFocusedWindow) {
+ mFocusedContent = nullptr;
+ } else {
+ // Check if the node that was focused is an iframe or similar by looking
+ // if it has a subdocument. This would indicate that this focused iframe
+ // and its descendants will be going away. We will need to move the
+ // focus somewhere else, so just clear the focus in the toplevel window
+ // so that no element is focused.
+ nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
+ if (subdoc) {
+ nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
+ if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
+ ClearFocus(mActiveWindow);
+ }
+ }
+ }
+ }
+
+ // Notify the editor in case we removed its ancestor limiter.
+ if (content->IsEditable()) {
+ nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIEditor> editor;
+ docShell->GetEditor(getter_AddRefs(editor));
+ if (editor) {
+ nsCOMPtr<nsISelection> s;
+ editor->GetSelection(getter_AddRefs(s));
+ nsCOMPtr<nsISelectionPrivate> selection = do_QueryInterface(s);
+ if (selection) {
+ nsCOMPtr<nsIContent> limiter;
+ selection->GetAncestorLimiter(getter_AddRefs(limiter));
+ if (limiter == content) {
+ editor->FinalizeSelection();
+ }
+ }
+ }
+ }
+ }
+
+ NotifyFocusStateChange(content, shouldShowFocusRing, false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Shown [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS(("Shown Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+
+ if (mFocusedWindow) {
+ doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (nsIDocShell* docShell = window->GetDocShell()) {
+ if (nsCOMPtr<nsITabChild> child = docShell->GetTabChild()) {
+ bool active = static_cast<TabChild*>(child.get())->ParentIsActive();
+ ActivateOrDeactivate(window, active);
+ }
+ }
+
+ if (mFocusedWindow != window)
+ return NS_OK;
+
+ if (aNeedsFocus) {
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
+ nsCOMPtr<nsIContent> currentFocus =
+ GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
+ if (currentWindow)
+ Focus(currentWindow, currentFocus, 0, true, false, false, true);
+ }
+ else {
+ // Sometimes, an element in a window can be focused before the window is
+ // visible, which would mean that the widget may not be properly focused.
+ // When the window becomes visible, make sure the right widget is focused.
+ EnsureCurrentWidgetFocused();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow)
+{
+ // if there is no window or it is not the same or an ancestor of the
+ // currently focused window, just return, as the current focus will not
+ // be affected.
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
+ nsAutoCString spec;
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Hide Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+
+ if (mFocusedWindow) {
+ doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+
+ if (mActiveWindow) {
+ doc = mActiveWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Active Window: %s",
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+ }
+
+ if (!IsSameOrAncestor(window, mFocusedWindow))
+ return NS_OK;
+
+ // at this point, we know that the window being hidden is either the focused
+ // window, or an ancestor of the focused window. Either way, the focus is no
+ // longer valid, so it needs to be updated.
+
+ nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget();
+
+ nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
+ nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
+
+ if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) {
+ NotifyFocusStateChange(oldFocusedContent,
+ mFocusedWindow->ShouldShowFocusRing(),
+ false);
+ window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ if (presShell) {
+ SendFocusOrBlurEvent(eBlur, presShell,
+ oldFocusedContent->GetComposedDoc(),
+ oldFocusedContent, 1, false);
+ }
+ }
+
+ nsPresContext* focusedPresContext =
+ presShell ? presShell->GetPresContext() : nullptr;
+ IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
+ GetFocusMoveActionCause(0));
+ if (presShell) {
+ SetCaretVisible(presShell, false, nullptr);
+ }
+
+ // if the docshell being hidden is being destroyed, then we want to move
+ // focus somewhere else. Call ClearFocus on the toplevel window, which
+ // will have the effect of clearing the focus and moving the focused window
+ // to the toplevel window. But if the window isn't being destroyed, we are
+ // likely just loading a new document in it, so we want to maintain the
+ // focused window so that the new document gets properly focused.
+ nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
+ bool beingDestroyed = !docShellBeingHidden;
+ if (docShellBeingHidden) {
+ docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
+ }
+ if (beingDestroyed) {
+ // There is usually no need to do anything if a toplevel window is going
+ // away, as we assume that WindowLowered will be called. However, this may
+ // not happen if nsIAppStartup::eForceQuit is used to quit, and can cause
+ // a leak. So if the active window is being destroyed, call WindowLowered
+ // directly.
+ NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
+ if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
+ WindowLowered(mActiveWindow);
+ else
+ ClearFocus(mActiveWindow);
+ return NS_OK;
+ }
+
+ // if the window being hidden is an ancestor of the focused window, adjust
+ // the focused window so that it points to the one being hidden. This
+ // ensures that the focused window isn't in a chain of frames that doesn't
+ // exist any more.
+ if (window != mFocusedWindow) {
+ nsCOMPtr<nsIDocShellTreeItem> dsti =
+ mFocusedWindow ? mFocusedWindow->GetDocShell() : nullptr;
+ if (dsti) {
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ dsti->GetParent(getter_AddRefs(parentDsti));
+ if (parentDsti) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parentDsti->GetWindow())
+ parentWindow->SetFocusedNode(nullptr);
+ }
+ }
+
+ SetFocusedWindowInternal(window);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
+{
+ NS_ENSURE_ARG(aDocument);
+
+ // fire any delayed focus and blur events in the same order that they were added
+ for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
+ if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
+ if (!aDocument->GetInnerWindow() ||
+ !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
+ // If the document was navigated away from or is defunct, don't bother
+ // firing events on it. Note the symmetry between this condition and
+ // the similar one in nsDocument.cpp:FireOrClearDelayedEvents.
+ mDelayedBlurFocusEvents.RemoveElementAt(i);
+ --i;
+ } else if (!aDocument->EventHandlingSuppressed()) {
+ EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
+ nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
+ nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
+ nsCOMPtr<EventTarget> relatedTarget = mDelayedBlurFocusEvents[i].mRelatedTarget;
+ mDelayedBlurFocusEvents.RemoveElementAt(i);
+ SendFocusOrBlurEvent(message, presShell, aDocument, target, 0,
+ false, false, relatedTarget);
+ --i;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::FocusPlugin(nsIContent* aContent)
+{
+ NS_ENSURE_ARG(aContent);
+ SetFocusInner(aContent, 0, true, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
+
+ ActivateOrDeactivate(window, aActive);
+ return NS_OK;
+}
+
+/* static */
+void
+nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
+ bool aWindowShouldShowFocusRing,
+ bool aGettingFocus)
+{
+ if (!aContent->IsElement()) {
+ return;
+ }
+ EventStates eventState = NS_EVENT_STATE_FOCUS;
+ if (aWindowShouldShowFocusRing) {
+ eventState |= NS_EVENT_STATE_FOCUSRING;
+ }
+
+ if (aGettingFocus) {
+ aContent->AsElement()->AddStates(eventState);
+ } else {
+ aContent->AsElement()->RemoveStates(eventState);
+ }
+
+ for (Element* element = aContent->AsElement(); element;
+ element = element->GetParentElementCrossingShadowRoot()) {
+ if (aGettingFocus) {
+ element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN);
+ } else {
+ element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN);
+ }
+ }
+}
+
+// static
+void
+nsFocusManager::EnsureCurrentWidgetFocused()
+{
+ if (!mFocusedWindow || sTestMode)
+ return;
+
+ // get the main child widget for the focused window and ensure that the
+ // platform knows that this widget is focused.
+ nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (presShell) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(false);
+ }
+ }
+ }
+}
+
+bool
+ActivateOrDeactivateChild(TabParent* aParent, void* aArg)
+{
+ bool active = static_cast<bool>(aArg);
+ Unused << aParent->SendParentActivated(active);
+ return false;
+}
+
+void
+nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive)
+{
+ if (!aWindow) {
+ return;
+ }
+
+ // Inform the DOM window that it has activated or deactivated, so that
+ // the active attribute is updated on the window.
+ aWindow->ActivateOrDeactivate(aActive);
+
+ // Send the activate event.
+ if (aWindow->GetExtantDoc()) {
+ nsContentUtils::DispatchEventOnlyToChrome(aWindow->GetExtantDoc(),
+ aWindow->GetCurrentInnerWindow(),
+ aActive ?
+ NS_LITERAL_STRING("activate") :
+ NS_LITERAL_STRING("deactivate"),
+ true, true, nullptr);
+ }
+
+ // Look for any remote child frames, iterate over them and send the activation notification.
+ nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild,
+ (void *)aActive);
+}
+
+void
+nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
+ bool aFocusChanged, bool aAdjustWidget)
+{
+ // if the element is not focusable, just return and leave the focus as is
+ nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags);
+ if (!contentToFocus)
+ return;
+
+ // check if the element to focus is a frame (iframe) containing a child
+ // document. Frames are never directly focused; instead focusing a frame
+ // means focus what is inside the frame. To do this, the descendant content
+ // within the frame is retrieved and that will be focused instead.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow;
+ nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(contentToFocus);
+ if (subWindow) {
+ contentToFocus = GetFocusedDescendant(subWindow, true, getter_AddRefs(newWindow));
+ // since a window is being refocused, clear aFocusChanged so that the
+ // caret position isn't updated.
+ aFocusChanged = false;
+ }
+
+ // unless it was set above, retrieve the window for the element to focus
+ if (!newWindow)
+ newWindow = GetCurrentWindow(contentToFocus);
+
+ // if the element is already focused, just return. Note that this happens
+ // after the frame check above so that we compare the element that will be
+ // focused rather than the frame it is in.
+ if (!newWindow || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent))
+ return;
+
+ // don't allow focus to be placed in docshells or descendants of docshells
+ // that are being destroyed. Also, ensure that the page hasn't been
+ // unloaded. The prevents content from being refocused during an unload event.
+ nsCOMPtr<nsIDocShell> newDocShell = newWindow->GetDocShell();
+ nsCOMPtr<nsIDocShell> docShell = newDocShell;
+ while (docShell) {
+ bool inUnload;
+ docShell->GetIsInUnload(&inUnload);
+ if (inUnload)
+ return;
+
+ bool beingDestroyed;
+ docShell->IsBeingDestroyed(&beingDestroyed);
+ if (beingDestroyed)
+ return;
+
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ docShell->GetParent(getter_AddRefs(parentDsti));
+ docShell = do_QueryInterface(parentDsti);
+ }
+
+ // if the new element is in the same window as the currently focused element
+ bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
+
+ if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
+ nsContentUtils::IsHandlingKeyBoardEvent()) {
+ nsCOMPtr<nsIScriptObjectPrincipal> focused =
+ do_QueryInterface(mFocusedWindow);
+ nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
+ do_QueryInterface(newWindow);
+ nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
+ nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
+ if (!focusedPrincipal || !newPrincipal) {
+ return;
+ }
+ bool subsumes = false;
+ focusedPrincipal->Subsumes(newPrincipal, &subsumes);
+ if (!subsumes && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ NS_WARNING("Not allowed to focus the new window!");
+ return;
+ }
+ }
+
+ // to check if the new element is in the active window, compare the
+ // new root docshell for the new element with the active window's docshell.
+ bool isElementInActiveWindow = false;
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
+ nsCOMPtr<nsPIDOMWindowOuter> newRootWindow;
+ if (dsti) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ dsti->GetRootTreeItem(getter_AddRefs(root));
+ newRootWindow = root ? root->GetWindow() : nullptr;
+
+ isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
+ }
+
+ // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
+ // system. We don't control event dispatch to windowed plugins on non-MacOSX,
+ // so we can't display the "Press ESC to leave fullscreen mode" warning on
+ // key input if a windowed plugin is focused, so just exit fullscreen
+ // to guard against phishing.
+#ifndef XP_MACOSX
+ if (contentToFocus &&
+ nsContentUtils::
+ GetRootDocument(contentToFocus->OwnerDoc())->GetFullscreenElement() &&
+ nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ contentToFocus->OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "FocusedWindowedPluginWhileFullscreen");
+ nsIDocument::AsyncExitFullscreen(contentToFocus->OwnerDoc());
+ }
+#endif
+
+ // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
+ // shifted away from the current element if the new shell to focus is
+ // the same or an ancestor shell of the currently focused shell.
+ bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
+ IsSameOrAncestor(newWindow, mFocusedWindow);
+
+ // if the element is in the active window, frame switching is allowed and
+ // the content is in a visible window, fire blur and focus events.
+ bool sendFocusEvent =
+ isElementInActiveWindow && allowFrameSwitch && IsWindowVisible(newWindow);
+
+ // When the following conditions are true:
+ // * an element has focus
+ // * isn't called by trusted event (i.e., called by untrusted event or by js)
+ // * the focus is moved to another document's element
+ // we need to check the permission.
+ if (sendFocusEvent && mFocusedContent && !nsContentUtils::LegacyIsCallerNativeCode() &&
+ mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) {
+ // If the caller cannot access the current focused node, the caller should
+ // not be able to steal focus from it. E.g., When the current focused node
+ // is in chrome, any web contents should not be able to steal the focus.
+ nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
+ sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
+ if (!sendFocusEvent && mMouseButtonEventHandlingDocument) {
+ // However, while mouse button event is handling, the handling document's
+ // script should be able to steal focus.
+ domNode = do_QueryInterface(mMouseButtonEventHandlingDocument);
+ sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
+ }
+ }
+
+ LOGCONTENT("Shift Focus: %s", contentToFocus.get());
+ LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
+ aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get()));
+ LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
+ isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
+
+ if (sendFocusEvent) {
+ nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent;
+ // return if blurring fails or the focus changes during the blur
+ if (mFocusedWindow) {
+ // if the focus is being moved to another element in the same document,
+ // or to a descendant, pass the existing window to Blur so that the
+ // current node in the existing window is cleared. If moving to a
+ // window elsewhere, we want to maintain the current node in the
+ // window but still blur it.
+ bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
+ // find the common ancestor of the currently focused window and the new
+ // window. The ancestor will need to have its currently focused node
+ // cleared once the document has been blurred. Otherwise, we'll be in a
+ // state where a document is blurred yet the chain of windows above it
+ // still points to that document.
+ // For instance, in the following frame tree:
+ // A
+ // B C
+ // D
+ // D is focused and we want to focus C. Once D has been blurred, we need
+ // to clear out the focus in A, otherwise A would still maintain that B
+ // was focused, and B that D was focused.
+ nsCOMPtr<nsPIDOMWindowOuter> commonAncestor;
+ if (!isElementInFocusedWindow)
+ commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
+
+ if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
+ commonAncestor, !isElementInFocusedWindow, aAdjustWidget,
+ contentToFocus))
+ return;
+ }
+
+ Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
+ aFocusChanged, false, aAdjustWidget, oldFocusedContent);
+ }
+ else {
+ // otherwise, for inactive windows and when the caller cannot steal the
+ // focus, update the node in the window, and raise the window if desired.
+ if (allowFrameSwitch)
+ AdjustWindowFocus(newWindow, true);
+
+ // set the focus node and method as needed
+ uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
+ newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
+ newWindow->SetFocusedNode(contentToFocus, focusMethod);
+ if (aFocusChanged) {
+ nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (presShell && presShell->DidInitialize())
+ ScrollIntoView(presShell, contentToFocus, aFlags);
+ }
+
+ // update the commands even when inactive so that the attributes for that
+ // window are up to date.
+ if (allowFrameSwitch)
+ newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ if (aFlags & FLAG_RAISE)
+ RaiseWindow(newRootWindow);
+ }
+}
+
+bool
+nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
+ nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow || !aPossibleAncestor) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell();
+ nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell();
+ while (dsti) {
+ if (dsti == ancestordsti)
+ return true;
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ dsti->GetParent(getter_AddRefs(parentDsti));
+ dsti.swap(parentDsti);
+ }
+
+ return false;
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsFocusManager::GetCommonAncestor(nsPIDOMWindowOuter* aWindow1,
+ nsPIDOMWindowOuter* aWindow2)
+{
+ NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell();
+ NS_ENSURE_TRUE(dsti1, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell();
+ NS_ENSURE_TRUE(dsti2, nullptr);
+
+ AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(dsti1);
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
+ dsti1->GetParent(getter_AddRefs(parentDsti1));
+ dsti1.swap(parentDsti1);
+ } while (dsti1);
+ do {
+ parents2.AppendElement(dsti2);
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
+ dsti2->GetParent(getter_AddRefs(parentDsti2));
+ dsti2.swap(parentDsti2);
+ } while (dsti2);
+
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ nsIDocShellTreeItem* parent = nullptr;
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ nsIDocShellTreeItem* child1 = parents1.ElementAt(--pos1);
+ nsIDocShellTreeItem* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ break;
+ }
+ parent = child1;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = parent ? parent->GetWindow() : nullptr;
+ return window.forget();
+}
+
+void
+nsFocusManager::AdjustWindowFocus(nsPIDOMWindowOuter* aWindow,
+ bool aCheckPermission)
+{
+ bool isVisible = IsWindowVisible(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
+ while (window) {
+ // get the containing <iframe> or equivalent element so that it can be
+ // focused below.
+ nsCOMPtr<Element> frameElement = window->GetFrameElementInternal();
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell();
+ if (!dsti)
+ return;
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ dsti->GetParent(getter_AddRefs(parentDsti));
+ if (!parentDsti) {
+ return;
+ }
+
+ window = parentDsti->GetWindow();
+ if (window) {
+ // if the parent window is visible but aWindow was not, then we have
+ // likely moved up and out from a hidden tab to the browser window, or a
+ // similar such arrangement. Stop adjusting the current nodes.
+ if (IsWindowVisible(window) != isVisible)
+ break;
+
+ // When aCheckPermission is true, we should check whether the caller can
+ // access the window or not. If it cannot access, we should stop the
+ // adjusting.
+ if (aCheckPermission && !nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(window->GetCurrentInnerWindow())) {
+ break;
+ }
+
+ window->SetFocusedNode(frameElement);
+ }
+ }
+}
+
+bool
+nsFocusManager::IsWindowVisible(nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow || aWindow->IsFrozen())
+ return false;
+
+ // Check if the inner window is frozen as well. This can happen when a focus change
+ // occurs while restoring a previous page.
+ nsPIDOMWindowInner* innerWindow = aWindow->GetCurrentInnerWindow();
+ if (!innerWindow || innerWindow->IsFrozen())
+ return false;
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(docShell));
+ if (!baseWin)
+ return false;
+
+ bool visible = false;
+ baseWin->GetVisibility(&visible);
+ return visible;
+}
+
+bool
+nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
+{
+ NS_PRECONDITION(aContent, "aContent must not be NULL");
+ NS_PRECONDITION(aContent->IsInComposedDoc(), "aContent must be in a document");
+
+ // If aContent is in designMode, the root element is not focusable.
+ // NOTE: in designMode, most elements are not focusable, just the document is
+ // focusable.
+ // Also, if aContent is not editable but it isn't in designMode, it's not
+ // focusable.
+ // And in userfocusignored context nothing is focusable.
+ nsIDocument* doc = aContent->GetComposedDoc();
+ NS_ASSERTION(doc, "aContent must have current document");
+ return aContent == doc->GetRootElement() &&
+ (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
+ nsContentUtils::IsUserFocusIgnored(aContent));
+}
+
+nsIContent*
+nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
+{
+ if (!aContent)
+ return nullptr;
+
+ // this is a special case for some XUL elements or input number, where an
+ // anonymous child is actually focusable and not the element itself.
+ nsCOMPtr<nsIContent> redirectedFocus = GetRedirectedFocus(aContent);
+ if (redirectedFocus)
+ return CheckIfFocusable(redirectedFocus, aFlags);
+
+ nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
+ // can't focus elements that are not in documents
+ if (!doc) {
+ LOGCONTENT("Cannot focus %s because content not in document", aContent)
+ return nullptr;
+ }
+
+ // Make sure that our frames are up to date
+ doc->FlushPendingNotifications(Flush_Layout);
+
+ nsIPresShell *shell = doc->GetShell();
+ if (!shell)
+ return nullptr;
+
+ // the root content can always be focused,
+ // except in userfocusignored context.
+ if (aContent == doc->GetRootElement())
+ return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent;
+
+ // cannot focus content in print preview mode. Only the root can be focused.
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
+ LOGCONTENT("Cannot focus %s while in print preview", aContent)
+ return nullptr;
+ }
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ LOGCONTENT("Cannot focus %s as it has no frame", aContent)
+ return nullptr;
+ }
+
+ if (aContent->IsHTMLElement(nsGkAtoms::area)) {
+ // HTML areas do not have their own frame, and the img frame we get from
+ // GetPrimaryFrame() is not relevant as to whether it is focusable or
+ // not, so we have to do all the relevant checks manually for them.
+ return frame->IsVisibleConsideringAncestors() &&
+ aContent->IsFocusable() ? aContent : nullptr;
+ }
+
+ // if this is a child frame content node, check if it is visible and
+ // call the content node's IsFocusable method instead of the frame's
+ // IsFocusable method. This skips checking the style system and ensures that
+ // offscreen browsers can still be focused.
+ nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+ if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
+ const nsStyleUserInterface* ui = frame->StyleUserInterface();
+ int32_t tabIndex = (ui->mUserFocus == StyleUserFocus::Ignore ||
+ ui->mUserFocus == StyleUserFocus::None) ? -1 : 0;
+ return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
+ }
+
+ return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
+}
+
+bool
+nsFocusManager::Blur(nsPIDOMWindowOuter* aWindowToClear,
+ nsPIDOMWindowOuter* aAncestorWindowToFocus,
+ bool aIsLeavingDocument,
+ bool aAdjustWidgets,
+ nsIContent* aContentToFocus)
+{
+ LOGFOCUS(("<<Blur begin>>"));
+
+ // hold a reference to the focused content, which may be null
+ nsCOMPtr<nsIContent> content = mFocusedContent;
+ if (content) {
+ if (!content->IsInComposedDoc()) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+ if (content == mFirstBlurEvent)
+ return true;
+ }
+
+ // hold a reference to the focused window
+ nsCOMPtr<nsPIDOMWindowOuter> window = mFocusedWindow;
+ if (!window) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (!docShell) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+
+ // Keep a ref to presShell since dispatching the DOM event may cause
+ // the document to be destroyed.
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+
+ bool clearFirstBlurEvent = false;
+ if (!mFirstBlurEvent) {
+ mFirstBlurEvent = content;
+ clearFirstBlurEvent = true;
+ }
+
+ nsPresContext* focusedPresContext =
+ mActiveWindow ? presShell->GetPresContext() : nullptr;
+ IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
+ GetFocusMoveActionCause(0));
+
+ // now adjust the actual focus, by clearing the fields in the focus manager
+ // and in the window.
+ mFocusedContent = nullptr;
+ bool shouldShowFocusRing = window->ShouldShowFocusRing();
+ if (aWindowToClear)
+ aWindowToClear->SetFocusedNode(nullptr);
+
+ LOGCONTENT("Element %s has been blurred", content.get());
+
+ // Don't fire blur event on the root content which isn't editable.
+ bool sendBlurEvent =
+ content && content->IsInComposedDoc() && !IsNonFocusableRoot(content);
+ if (content) {
+ if (sendBlurEvent) {
+ NotifyFocusStateChange(content, shouldShowFocusRing, false);
+ }
+
+ // if an object/plug-in/remote browser is being blurred, move the system focus
+ // to the parent window, otherwise events will still get fired at the plugin.
+ // But don't do this if we are blurring due to the window being lowered,
+ // otherwise, the parent window can get raised again.
+ if (mActiveWindow) {
+ nsIFrame* contentFrame = content->GetPrimaryFrame();
+ nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
+ if (aAdjustWidgets && objectFrame && !sTestMode) {
+ if (XRE_IsContentProcess()) {
+ // set focus to the top level window via the chrome process.
+ nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
+ if (tabChild) {
+ static_cast<TabChild*>(tabChild.get())->SendDispatchFocusToTopLevelWindow();
+ }
+ } else {
+ // note that the presshell's widget is being retrieved here, not the one
+ // for the object frame.
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget) {
+ // set focus to the top level window but don't raise it.
+ widget->SetFocus(false);
+ }
+ }
+ }
+ }
+ }
+
+ // if the object being blurred is a remote browser, deactivate remote content
+ if (TabParent* remote = TabParent::GetFrom(content)) {
+ remote->Deactivate();
+ LOGFOCUS(("Remote browser deactivated"));
+ }
+ }
+
+ bool result = true;
+ if (sendBlurEvent) {
+ // if there is an active window, update commands. If there isn't an active
+ // window, then this was a blur caused by the active window being lowered,
+ // so there is no need to update the commands
+ if (mActiveWindow)
+ window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ SendFocusOrBlurEvent(eBlur, presShell,
+ content->GetComposedDoc(), content, 1,
+ false, false, aContentToFocus);
+ }
+
+ // if we are leaving the document or the window was lowered, make the caret
+ // invisible.
+ if (aIsLeavingDocument || !mActiveWindow) {
+ SetCaretVisible(presShell, false, nullptr);
+ }
+
+ RefPtr<AccessibleCaretEventHub> eventHub = presShell->GetAccessibleCaretEventHub();
+ if (eventHub) {
+ eventHub->NotifyBlur(aIsLeavingDocument || !mActiveWindow);
+ }
+
+ // at this point, it is expected that this window will be still be
+ // focused, but the focused content will be null, as it was cleared before
+ // the event. If this isn't the case, then something else was focused during
+ // the blur event above and we should just return. However, if
+ // aIsLeavingDocument is set, a new document is desired, so make sure to
+ // blur the document and window.
+ if (mFocusedWindow != window ||
+ (mFocusedContent != nullptr && !aIsLeavingDocument)) {
+ result = false;
+ }
+ else if (aIsLeavingDocument) {
+ window->TakeFocus(false, 0);
+
+ // clear the focus so that the ancestor frame hierarchy is in the correct
+ // state. Pass true because aAncestorWindowToFocus is thought to be
+ // focused at this point.
+ if (aAncestorWindowToFocus)
+ aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true);
+
+ SetFocusedWindowInternal(nullptr);
+ mFocusedContent = nullptr;
+
+ // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
+ // that the check is made for suppressed documents. Check to ensure that
+ // the document isn't null in case someone closed it during the blur above
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc)
+ SendFocusOrBlurEvent(eBlur, presShell, doc, doc, 1, false);
+ if (mFocusedWindow == nullptr)
+ SendFocusOrBlurEvent(eBlur, presShell, doc,
+ window->GetCurrentInnerWindow(), 1, false);
+
+ // check if a different window was focused
+ result = (mFocusedWindow == nullptr && mActiveWindow);
+ }
+ else if (mActiveWindow) {
+ // Otherwise, the blur of the element without blurring the document
+ // occurred normally. Call UpdateCaret to redisplay the caret at the right
+ // location within the document. This is needed to ensure that the caret
+ // used for caret browsing is made visible again when an input field is
+ // blurred.
+ UpdateCaret(false, true, nullptr);
+ }
+
+ if (clearFirstBlurEvent)
+ mFirstBlurEvent = nullptr;
+
+ return result;
+}
+
+void
+nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aContent,
+ uint32_t aFlags,
+ bool aIsNewDocument,
+ bool aFocusChanged,
+ bool aWindowRaised,
+ bool aAdjustWidgets,
+ nsIContent* aContentLostFocus)
+{
+ LOGFOCUS(("<<Focus begin>>"));
+
+ if (!aWindow)
+ return;
+
+ if (aContent && (aContent == mFirstFocusEvent || aContent == mFirstBlurEvent))
+ return;
+
+ // Keep a reference to the presShell since dispatching the DOM event may
+ // cause the document to be destroyed.
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (!docShell)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell)
+ return;
+
+ // If the focus actually changed, set the focus method (mouse, keyboard, etc).
+ // Otherwise, just get the current focus method and use that. This ensures
+ // that the method is set during the document and window focus events.
+ uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
+ aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
+
+ if (!IsWindowVisible(aWindow)) {
+ // if the window isn't visible, for instance because it is a hidden tab,
+ // update the current focus and scroll it into view but don't do anything else
+ if (CheckIfFocusable(aContent, aFlags)) {
+ aWindow->SetFocusedNode(aContent, focusMethod);
+ if (aFocusChanged)
+ ScrollIntoView(presShell, aContent, aFlags);
+ }
+ return;
+ }
+
+ bool clearFirstFocusEvent = false;
+ if (!mFirstFocusEvent) {
+ mFirstFocusEvent = aContent;
+ clearFirstFocusEvent = true;
+ }
+
+ LOGCONTENT("Element %s has been focused", aContent);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ nsIDocument* docm = aWindow->GetExtantDoc();
+ if (docm) {
+ LOGCONTENT(" from %s", docm->GetRootElement());
+ }
+ LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
+ aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
+ }
+
+ if (aIsNewDocument) {
+ // if this is a new document, update the parent chain of frames so that
+ // focus can be traversed from the top level down to the newly focused
+ // window.
+ AdjustWindowFocus(aWindow, false);
+ }
+
+ // indicate that the window has taken focus.
+ if (aWindow->TakeFocus(true, focusMethod))
+ aIsNewDocument = true;
+
+ SetFocusedWindowInternal(aWindow);
+
+ // Update the system focus by focusing the root widget. But avoid this
+ // if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
+ // own widget and is either already focused or is about to be focused.
+ nsCOMPtr<nsIWidget> objectFrameWidget;
+ if (aContent) {
+ nsIFrame* contentFrame = aContent->GetPrimaryFrame();
+ nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
+ if (objectFrame)
+ objectFrameWidget = objectFrame->GetWidget();
+ }
+ if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(false);
+ }
+ }
+
+ // if switching to a new document, first fire the focus event on the
+ // document and then the window.
+ if (aIsNewDocument) {
+ nsIDocument* doc = aWindow->GetExtantDoc();
+ // The focus change should be notified to IMEStateManager from here if
+ // the focused content is a designMode editor since any content won't
+ // receive focus event.
+ if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
+ IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
+ GetFocusMoveActionCause(aFlags));
+ }
+ if (doc)
+ SendFocusOrBlurEvent(eFocus, presShell, doc,
+ doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
+ if (mFocusedWindow == aWindow && mFocusedContent == nullptr)
+ SendFocusOrBlurEvent(eFocus, presShell, doc,
+ aWindow->GetCurrentInnerWindow(),
+ aFlags & FOCUSMETHOD_MASK, aWindowRaised);
+ }
+
+ // check to ensure that the element is still focusable, and that nothing
+ // else was focused during the events above.
+ if (CheckIfFocusable(aContent, aFlags) &&
+ mFocusedWindow == aWindow && mFocusedContent == nullptr) {
+ mFocusedContent = aContent;
+
+ nsIContent* focusedNode = aWindow->GetFocusedNode();
+ bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent);
+
+ aWindow->SetFocusedNode(aContent, focusMethod);
+
+ bool sendFocusEvent =
+ aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent);
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (sendFocusEvent) {
+ // if the focused element changed, scroll it into view
+ if (aFocusChanged)
+ ScrollIntoView(presShell, aContent, aFlags);
+
+ NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
+
+ // if this is an object/plug-in/remote browser, focus its widget. Note that we might
+ // no longer be in the same document, due to the events we fired above when
+ // aIsNewDocument.
+ if (presShell->GetDocument() == aContent->GetComposedDoc()) {
+ if (aAdjustWidgets && objectFrameWidget && !sTestMode)
+ objectFrameWidget->SetFocus(false);
+
+ // if the object being focused is a remote browser, activate remote content
+ if (TabParent* remote = TabParent::GetFrom(aContent)) {
+ remote->Activate();
+ LOGFOCUS(("Remote browser activated"));
+ }
+ }
+
+ IMEStateManager::OnChangeFocus(presContext, aContent,
+ GetFocusMoveActionCause(aFlags));
+
+ // as long as this focus wasn't because a window was raised, update the
+ // commands
+ // XXXndeakin P2 someone could adjust the focus during the update
+ if (!aWindowRaised)
+ aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ SendFocusOrBlurEvent(eFocus, presShell,
+ aContent->GetComposedDoc(),
+ aContent, aFlags & FOCUSMETHOD_MASK,
+ aWindowRaised, isRefocus, aContentLostFocus);
+ } else {
+ IMEStateManager::OnChangeFocus(presContext, nullptr,
+ GetFocusMoveActionCause(aFlags));
+ if (!aWindowRaised) {
+ aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+ }
+ }
+ }
+ else {
+ // If the window focus event (fired above when aIsNewDocument) caused
+ // the plugin not to be focusable, update the system focus by focusing
+ // the root widget.
+ if (aAdjustWidgets && objectFrameWidget &&
+ mFocusedWindow == aWindow && mFocusedContent == nullptr &&
+ !sTestMode) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(false);
+ }
+ }
+
+ if (!mFocusedContent) {
+ // When there is no focused content, IMEStateManager needs to adjust IME
+ // enabled state with the document.
+ nsPresContext* presContext = presShell->GetPresContext();
+ IMEStateManager::OnChangeFocus(presContext, nullptr,
+ GetFocusMoveActionCause(aFlags));
+ }
+
+ if (!aWindowRaised)
+ aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+ }
+
+ // update the caret visibility and position to match the newly focused
+ // element. However, don't update the position if this was a focus due to a
+ // mouse click as the selection code would already have moved the caret as
+ // needed. If this is a different document than was focused before, also
+ // update the caret's visibility. If this is the same document, the caret
+ // visibility should be the same as before so there is no need to update it.
+ if (mFocusedContent == aContent)
+ UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
+ mFocusedContent);
+
+ if (clearFirstFocusEvent)
+ mFirstFocusEvent = nullptr;
+}
+
+class FocusBlurEvent : public Runnable
+{
+public:
+ FocusBlurEvent(nsISupports* aTarget, EventMessage aEventMessage,
+ nsPresContext* aContext, bool aWindowRaised,
+ bool aIsRefocus, EventTarget* aRelatedTarget)
+ : mTarget(aTarget)
+ , mContext(aContext)
+ , mEventMessage(aEventMessage)
+ , mWindowRaised(aWindowRaised)
+ , mIsRefocus(aIsRefocus)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ InternalFocusEvent event(true, mEventMessage);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ event.mFromRaise = mWindowRaised;
+ event.mIsRefocus = mIsRefocus;
+ event.mRelatedTarget = mRelatedTarget;
+ return EventDispatcher::Dispatch(mTarget, mContext, &event);
+ }
+
+ nsCOMPtr<nsISupports> mTarget;
+ RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ bool mWindowRaised;
+ bool mIsRefocus;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+class FocusInOutEvent : public Runnable
+{
+public:
+ FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage,
+ nsPresContext* aContext,
+ nsPIDOMWindowOuter* aOriginalFocusedWindow,
+ nsIContent* aOriginalFocusedContent,
+ EventTarget* aRelatedTarget)
+ : mTarget(aTarget)
+ , mContext(aContext)
+ , mEventMessage(aEventMessage)
+ , mOriginalFocusedWindow(aOriginalFocusedWindow)
+ , mOriginalFocusedContent(aOriginalFocusedContent)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ?
+ mOriginalFocusedWindow->GetFocusedNode() :
+ nullptr;
+ // Blink does not check that focus is the same after blur, but WebKit does.
+ // Opt to follow Blink's behavior (see bug 687787).
+ if (mEventMessage == eFocusOut ||
+ originalWindowFocus == mOriginalFocusedContent) {
+ InternalFocusEvent event(true, mEventMessage);
+ event.mFlags.mBubbles = true;
+ event.mFlags.mCancelable = false;
+ event.mRelatedTarget = mRelatedTarget;
+ return EventDispatcher::Dispatch(mTarget, mContext, &event);
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> mTarget;
+ RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
+ nsCOMPtr<nsIContent> mOriginalFocusedContent;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+static nsIDocument*
+GetDocumentHelper(EventTarget* aTarget)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
+ if (!node) {
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget);
+ return win ? win->GetExtantDoc() : nullptr;
+ }
+
+ return node->OwnerDoc();
+}
+
+void nsFocusManager::SendFocusInOrOutEvent(EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsISupports* aTarget,
+ nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent,
+ EventTarget* aRelatedTarget)
+{
+ NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
+ "Wrong event type for SendFocusInOrOutEvent");
+
+ nsContentUtils::AddScriptRunner(
+ new FocusInOutEvent(
+ aTarget,
+ aEventMessage,
+ aPresShell->GetPresContext(),
+ aCurrentFocusedWindow,
+ aCurrentFocusedContent,
+ aRelatedTarget));
+}
+
+void
+nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsIDocument* aDocument,
+ nsISupports* aTarget,
+ uint32_t aFocusMethod,
+ bool aWindowRaised,
+ bool aIsRefocus,
+ EventTarget* aRelatedTarget)
+{
+ NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
+ "Wrong event type for SendFocusOrBlurEvent");
+
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget);
+ nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
+ nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ?
+ currentWindow->GetFocusedNode() : nullptr;
+
+ // set aRelatedTarget to null if it's not in the same document as eventTarget
+ if (eventTargetDoc != relatedTargetDoc) {
+ aRelatedTarget = nullptr;
+ }
+
+ bool dontDispatchEvent =
+ eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
+
+ // for focus events, if this event was from a mouse or key and event
+ // handling on the document is suppressed, queue the event and fire it
+ // later. For blur events, a non-zero value would be set for aFocusMethod.
+ if (aFocusMethod && !dontDispatchEvent &&
+ aDocument && aDocument->EventHandlingSuppressed()) {
+ // aFlags is always 0 when aWindowRaised is true so this won't be called
+ // on a window raise.
+ NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
+
+ for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
+ // if this event was already queued, remove it and append it to the end
+ if (mDelayedBlurFocusEvents[i - 1].mEventMessage == aEventMessage &&
+ mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
+ mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
+ mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget &&
+ mDelayedBlurFocusEvents[i - 1].mRelatedTarget == aRelatedTarget) {
+ mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
+ }
+ }
+
+ mDelayedBlurFocusEvents.AppendElement(
+ nsDelayedBlurOrFocusEvent(aEventMessage, aPresShell,
+ aDocument, eventTarget, aRelatedTarget));
+ return;
+ }
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ if (aEventMessage == eFocus) {
+ accService->NotifyOfDOMFocus(aTarget);
+ } else {
+ accService->NotifyOfDOMBlur(aTarget);
+ }
+ }
+#endif
+
+ if (!dontDispatchEvent) {
+ nsContentUtils::AddScriptRunner(
+ new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
+ aWindowRaised, aIsRefocus, aRelatedTarget));
+
+ // Check that the target is not a window or document before firing
+ // focusin/focusout. Other browsers do not fire focusin/focusout on window,
+ // despite being required in the spec, so follow their behavior.
+ //
+ // As for document, we should not even fire focus/blur, but until then, we
+ // need this check. targetDocument should be removed once bug 1228802 is
+ // resolved.
+ if (!targetWindow && !targetDocument) {
+ EventMessage focusInOrOutMessage = aEventMessage == eFocus ? eFocusIn : eFocusOut;
+ SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
+ currentWindow, currentFocusedContent, aRelatedTarget);
+ }
+ }
+}
+
+void
+nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ uint32_t aFlags)
+{
+ // if the noscroll flag isn't set, scroll the newly focused element into view
+ if (!(aFlags & FLAG_NOSCROLL))
+ aPresShell->ScrollContentIntoView(aContent,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+}
+
+
+void
+nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow)
+{
+ // don't raise windows that are already raised or are in the process of
+ // being lowered
+ if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
+ return;
+
+ if (sTestMode) {
+ // In test mode, emulate the existing window being lowered and the new
+ // window being raised.
+ if (mActiveWindow)
+ WindowLowered(mActiveWindow);
+ WindowRaised(aWindow);
+ return;
+ }
+
+#if defined(XP_WIN)
+ // Windows would rather we focus the child widget, otherwise, the toplevel
+ // widget will always end up being focused. Fortunately, focusing the child
+ // widget will also have the effect of raising the window this widget is in.
+ // But on other platforms, we can just focus the toplevel widget to raise
+ // the window.
+ nsCOMPtr<nsPIDOMWindowOuter> childWindow;
+ GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow));
+ if (!childWindow)
+ childWindow = aWindow;
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (!docShell)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell)
+ return;
+
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(true);
+ }
+#else
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
+ do_QueryInterface(aWindow->GetDocShell());
+ if (treeOwnerAsWin) {
+ nsCOMPtr<nsIWidget> widget;
+ treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(true);
+ }
+#endif
+}
+
+void
+nsFocusManager::UpdateCaretForCaretBrowsingMode()
+{
+ UpdateCaret(false, true, mFocusedContent);
+}
+
+void
+nsFocusManager::UpdateCaret(bool aMoveCaretToFocus,
+ bool aUpdateVisibility,
+ nsIContent* aContent)
+{
+ LOGFOCUS(("Update Caret: %d %d", aMoveCaretToFocus, aUpdateVisibility));
+
+ if (!mFocusedWindow)
+ return;
+
+ // this is called when a document is focused or when the caretbrowsing
+ // preference is changed
+ nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
+ nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
+ if (!dsti)
+ return;
+
+ if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return; // Never browse with caret in chrome
+ }
+
+ bool browseWithCaret =
+ Preferences::GetBool("accessibility.browsewithcaret");
+
+ nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
+ if (!presShell)
+ return;
+
+ // If this is an editable document which isn't contentEditable, or a
+ // contentEditable document and the node to focus is contentEditable,
+ // return, so that we don't mess with caret visibility.
+ bool isEditable = false;
+ focusedDocShell->GetEditable(&isEditable);
+
+ if (isEditable) {
+ nsCOMPtr<nsIHTMLDocument> doc =
+ do_QueryInterface(presShell->GetDocument());
+
+ bool isContentEditableDoc =
+ doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
+
+ bool isFocusEditable =
+ aContent && aContent->HasFlag(NODE_IS_EDITABLE);
+ if (!isContentEditableDoc || isFocusEditable)
+ return;
+ }
+
+ if (!isEditable && aMoveCaretToFocus)
+ MoveCaretToFocus(presShell, aContent);
+
+ if (!aUpdateVisibility)
+ return;
+
+ // XXXndeakin this doesn't seem right. It should be checking for this only
+ // on the nearest ancestor frame which is a chrome frame. But this is
+ // what the existing code does, so just leave it for now.
+ if (!browseWithCaret) {
+ nsCOMPtr<Element> docElement =
+ mFocusedWindow->GetFrameElementInternal();
+ if (docElement)
+ browseWithCaret = docElement->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::showcaret,
+ NS_LITERAL_STRING("true"),
+ eCaseMatters);
+ }
+
+ SetCaretVisible(presShell, browseWithCaret, aContent);
+}
+
+void
+nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
+{
+ // domDoc is a document interface we can create a range with
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aPresShell->GetDocument());
+ if (domDoc) {
+ RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
+ nsCOMPtr<nsISelection> domSelection =
+ frameSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
+ // First clear the selection. This way, if there is no currently focused
+ // content, the selection will just be cleared.
+ domSelection->RemoveAllRanges();
+ if (currentFocusNode) {
+ nsCOMPtr<nsIDOMRange> newRange;
+ nsresult rv = domDoc->CreateRange(getter_AddRefs(newRange));
+ if (NS_SUCCEEDED(rv)) {
+ // Set the range to the start of the currently focused node
+ // Make sure it's collapsed
+ newRange->SelectNodeContents(currentFocusNode);
+ nsCOMPtr<nsIDOMNode> firstChild;
+ currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
+ if (!firstChild ||
+ aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
+ // If current focus node is a leaf, set range to before the
+ // node by using the parent as a container.
+ // This prevents it from appearing as selected.
+ newRange->SetStartBefore(currentFocusNode);
+ newRange->SetEndBefore(currentFocusNode);
+ }
+ domSelection->AddRange(newRange);
+ domSelection->CollapseToStart();
+ }
+ }
+ }
+ }
+}
+
+nsresult
+nsFocusManager::SetCaretVisible(nsIPresShell* aPresShell,
+ bool aVisible,
+ nsIContent* aContent)
+{
+ // When browsing with caret, make sure caret is visible after new focus
+ // Return early if there is no caret. This can happen for the testcase
+ // for bug 308025 where a window is closed in a blur handler.
+ RefPtr<nsCaret> caret = aPresShell->GetCaret();
+ if (!caret)
+ return NS_OK;
+
+ bool caretVisible = caret->IsVisible();
+ if (!aVisible && !caretVisible)
+ return NS_OK;
+
+ RefPtr<nsFrameSelection> frameSelection;
+ if (aContent) {
+ NS_ASSERTION(aContent->GetComposedDoc() == aPresShell->GetDocument(),
+ "Wrong document?");
+ nsIFrame *focusFrame = aContent->GetPrimaryFrame();
+ if (focusFrame)
+ frameSelection = focusFrame->GetFrameSelection();
+ }
+
+ RefPtr<nsFrameSelection> docFrameSelection = aPresShell->FrameSelection();
+
+ if (docFrameSelection && caret &&
+ (frameSelection == docFrameSelection || !aContent)) {
+ nsISelection* domSelection =
+ docFrameSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
+ if (!selCon) {
+ return NS_ERROR_FAILURE;
+ }
+ // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
+ selCon->SetCaretEnabled(false);
+
+ // Caret must blink on non-editable elements
+ caret->SetIgnoreUserModify(true);
+ // Tell the caret which selection to use
+ caret->SetSelection(domSelection);
+
+ // In content, we need to set the caret. The only special case is edit
+ // fields, which have a different frame selection from the document.
+ // They will take care of making the caret visible themselves.
+
+ selCon->SetCaretReadOnly(false);
+ selCon->SetCaretEnabled(aVisible);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
+ nsIPresShell* aPresShell,
+ nsIContent **aStartContent,
+ nsIContent **aEndContent)
+{
+ *aStartContent = *aEndContent = nullptr;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ NS_ASSERTION(presContext, "mPresContent is null!!");
+
+ RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
+
+ nsCOMPtr<nsISelection> domSelection;
+ if (frameSelection) {
+ domSelection = frameSelection->GetSelection(SelectionType::eNormal);
+ }
+
+ nsCOMPtr<nsIDOMNode> startNode, endNode;
+ bool isCollapsed = false;
+ nsCOMPtr<nsIContent> startContent, endContent;
+ int32_t startOffset = 0;
+ if (domSelection) {
+ domSelection->GetIsCollapsed(&isCollapsed);
+ nsCOMPtr<nsIDOMRange> domRange;
+ rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
+ if (domRange) {
+ domRange->GetStartContainer(getter_AddRefs(startNode));
+ domRange->GetEndContainer(getter_AddRefs(endNode));
+ domRange->GetStartOffset(&startOffset);
+
+ nsIContent *childContent = nullptr;
+
+ startContent = do_QueryInterface(startNode);
+ if (startContent && startContent->IsElement()) {
+ NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
+ childContent = startContent->GetChildAt(startOffset);
+ if (childContent) {
+ startContent = childContent;
+ }
+ }
+
+ endContent = do_QueryInterface(endNode);
+ if (endContent && endContent->IsElement()) {
+ int32_t endOffset = 0;
+ domRange->GetEndOffset(&endOffset);
+ NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
+ childContent = endContent->GetChildAt(endOffset);
+ if (childContent) {
+ endContent = childContent;
+ }
+ }
+ }
+ }
+ else {
+ rv = NS_ERROR_INVALID_ARG;
+ }
+
+ nsIFrame *startFrame = nullptr;
+ if (startContent) {
+ startFrame = startContent->GetPrimaryFrame();
+ if (isCollapsed) {
+ // Next check to see if our caret is at the very end of a node
+ // If so, the caret is actually sitting in front of the next
+ // logical frame's primary node - so for this case we need to
+ // change caretContent to that node.
+
+ if (startContent->NodeType() == nsIDOMNode::TEXT_NODE) {
+ nsAutoString nodeValue;
+ startContent->AppendTextTo(nodeValue);
+
+ bool isFormControl =
+ startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
+
+ if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl &&
+ startContent != aDocument->GetRootElement()) {
+ // Yes, indeed we were at the end of the last node
+ nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+ nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
+ presContext, startFrame,
+ eLeaf,
+ false, // aVisual
+ false, // aLockInScrollView
+ true, // aFollowOOFs
+ false // aSkipPopupChecks
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIFrame *newCaretFrame = nullptr;
+ nsCOMPtr<nsIContent> newCaretContent = startContent;
+ bool endOfSelectionInStartNode(startContent == endContent);
+ do {
+ // Continue getting the next frame until the primary content for the frame
+ // we are on changes - we don't want to be stuck in the same place
+ frameTraversal->Next();
+ newCaretFrame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ if (nullptr == newCaretFrame)
+ break;
+ newCaretContent = newCaretFrame->GetContent();
+ } while (!newCaretContent || newCaretContent == startContent);
+
+ if (newCaretFrame && newCaretContent) {
+ // If the caret is exactly at the same position of the new frame,
+ // then we can use the newCaretFrame and newCaretContent for our position
+ nsRect caretRect;
+ nsIFrame *frame = nsCaret::GetGeometry(domSelection, &caretRect);
+ if (frame) {
+ nsPoint caretWidgetOffset;
+ nsIWidget *widget = frame->GetNearestWidget(caretWidgetOffset);
+ caretRect.MoveBy(caretWidgetOffset);
+ nsPoint newCaretOffset;
+ nsIWidget *newCaretWidget = newCaretFrame->GetNearestWidget(newCaretOffset);
+ if (widget == newCaretWidget && caretRect.y == newCaretOffset.y &&
+ caretRect.x == newCaretOffset.x) {
+ // The caret is at the start of the new element.
+ startFrame = newCaretFrame;
+ startContent = newCaretContent;
+ if (endOfSelectionInStartNode) {
+ endContent = newCaretContent; // Ensure end of selection is not before start
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ *aStartContent = startContent;
+ *aEndContent = endContent;
+ NS_IF_ADDREF(*aStartContent);
+ NS_IF_ADDREF(*aEndContent);
+
+ return rv;
+}
+
+nsresult
+nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aStartContent,
+ int32_t aType, bool aNoParentTraversal,
+ nsIContent** aNextContent)
+{
+ *aNextContent = nullptr;
+
+ // True if we are navigating by document (F6/Shift+F6) or false if we are
+ // navigating by element (Tab/Shift+Tab).
+ bool forDocumentNavigation = false;
+
+ // This is used for document navigation only. It will be set to true if we
+ // start navigating from a starting point. If this starting point is near the
+ // end of the document (for example, an element on a statusbar), and there
+ // are no child documents or panels before the end of the document, then we
+ // will need to ensure that we don't consider the root chrome window when we
+ // loop around and instead find the next child document/panel, as focus is
+ // already in that window. This flag will be cleared once we navigate into
+ // another document.
+ bool mayFocusRoot = (aStartContent != nullptr);
+
+ nsCOMPtr<nsIContent> startContent = aStartContent;
+ if (!startContent && aType != MOVEFOCUS_CARET) {
+ if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
+ // When moving between documents, make sure to get the right
+ // starting content in a descendant.
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
+ }
+ else if (aType != MOVEFOCUS_LASTDOC) {
+ // Otherwise, start at the focused node. If MOVEFOCUS_LASTDOC is used,
+ // then we are document-navigating backwards from chrome to the content
+ // process, and we don't want to use this so that we start from the end
+ // of the document.
+ startContent = aWindow->GetFocusedNode();
+ }
+ }
+
+ nsCOMPtr<nsIDocument> doc;
+ if (startContent)
+ doc = startContent->GetComposedDoc();
+ else
+ doc = aWindow->GetExtantDoc();
+ if (!doc)
+ return NS_OK;
+
+ LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
+ &nsIContent::sTabFocusModel);
+
+ // These types are for document navigation using F6.
+ if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
+ aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) {
+ forDocumentNavigation = true;
+ }
+
+ // If moving to the root or first document, find the root element and return.
+ if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
+ NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
+ if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
+ // When looking for the first document, if the root wasn't focusable,
+ // find the next focusable document.
+ aType = MOVEFOCUS_FORWARDDOC;
+ } else {
+ return NS_OK;
+ }
+ }
+
+ nsIContent* rootContent = doc->GetRootElement();
+ NS_ENSURE_TRUE(rootContent, NS_OK);
+
+ nsIPresShell *presShell = doc->GetShell();
+ NS_ENSURE_TRUE(presShell, NS_OK);
+
+ if (aType == MOVEFOCUS_FIRST) {
+ if (!aStartContent)
+ startContent = rootContent;
+ return GetNextTabbableContent(presShell, startContent,
+ nullptr, startContent,
+ true, 1, false, false, aNextContent);
+ }
+ if (aType == MOVEFOCUS_LAST) {
+ if (!aStartContent)
+ startContent = rootContent;
+ return GetNextTabbableContent(presShell, startContent,
+ nullptr, startContent,
+ false, 0, false, false, aNextContent);
+ }
+
+ bool forward = (aType == MOVEFOCUS_FORWARD ||
+ aType == MOVEFOCUS_FORWARDDOC ||
+ aType == MOVEFOCUS_CARET);
+ bool doNavigation = true;
+ bool ignoreTabIndex = false;
+ // when a popup is open, we want to ensure that tab navigation occurs only
+ // within the most recently opened panel. If a popup is open, its frame will
+ // be stored in popupFrame.
+ nsIFrame* popupFrame = nullptr;
+
+ int32_t tabIndex = forward ? 1 : 0;
+ if (startContent) {
+ nsIFrame* frame = startContent->GetPrimaryFrame();
+ if (startContent->IsHTMLElement(nsGkAtoms::area))
+ startContent->IsFocusable(&tabIndex);
+ else if (frame)
+ frame->IsFocusable(&tabIndex, 0);
+ else
+ startContent->IsFocusable(&tabIndex);
+
+ // if the current element isn't tabbable, ignore the tabindex and just
+ // look for the next element. The root content won't have a tabindex
+ // so just treat this as the beginning of the tab order.
+ if (tabIndex < 0) {
+ tabIndex = 1;
+ if (startContent != rootContent)
+ ignoreTabIndex = true;
+ }
+
+ // check if the focus is currently inside a popup. Elements such as the
+ // autocomplete widget use the noautofocus attribute to allow the focus to
+ // remain outside the popup when it is opened.
+ if (frame) {
+ popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
+ nsGkAtoms::menuPopupFrame);
+ }
+
+ if (popupFrame && !forDocumentNavigation) {
+ // Don't navigate outside of a popup, so pretend that the
+ // root content is the popup itself
+ rootContent = popupFrame->GetContent();
+ NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
+ }
+ else if (!forward) {
+ // If focus moves backward and when current focused node is root
+ // content or <body> element which is editable by contenteditable
+ // attribute, focus should move to its parent document.
+ if (startContent == rootContent) {
+ doNavigation = false;
+ } else {
+ nsIDocument* doc = startContent->GetComposedDoc();
+ if (startContent ==
+ nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
+ doNavigation = false;
+ }
+ }
+ }
+ }
+ else {
+#ifdef MOZ_XUL
+ if (aType != MOVEFOCUS_CARET) {
+ // if there is no focus, yet a panel is open, focus the first item in
+ // the panel
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ popupFrame = pm->GetTopPopup(ePopupTypePanel);
+ }
+#endif
+ if (popupFrame) {
+ // When there is a popup open, and no starting content, start the search
+ // at the topmost popup.
+ startContent = popupFrame->GetContent();
+ NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
+ // Unless we are searching for documents, set the root content to the
+ // popup as well, so that we don't tab-navigate outside the popup.
+ // When navigating by documents, we start at the popup but can navigate
+ // outside of it to look for other panels and documents.
+ if (!forDocumentNavigation) {
+ rootContent = startContent;
+ }
+
+ doc = startContent ? startContent->GetComposedDoc() : nullptr;
+ }
+ else {
+ // Otherwise, for content shells, start from the location of the caret.
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ nsCOMPtr<nsIContent> endSelectionContent;
+ GetSelectionLocation(doc, presShell,
+ getter_AddRefs(startContent),
+ getter_AddRefs(endSelectionContent));
+ // If the selection is on the rootContent, then there is no selection
+ if (startContent == rootContent) {
+ startContent = nullptr;
+ }
+
+ if (aType == MOVEFOCUS_CARET) {
+ // GetFocusInSelection finds a focusable link near the caret.
+ // If there is no start content though, don't do this to avoid
+ // focusing something unexpected.
+ if (startContent) {
+ GetFocusInSelection(aWindow, startContent,
+ endSelectionContent, aNextContent);
+ }
+ return NS_OK;
+ }
+
+ if (startContent) {
+ // when starting from a selection, we always want to find the next or
+ // previous element in the document. So the tabindex on elements
+ // should be ignored.
+ ignoreTabIndex = true;
+ }
+ }
+
+ if (!startContent) {
+ // otherwise, just use the root content as the starting point
+ startContent = rootContent;
+ NS_ENSURE_TRUE(startContent, NS_OK);
+ }
+ }
+ }
+
+ // Check if the starting content is the same as the content assigned to the
+ // retargetdocumentfocus attribute. Is so, we don't want to start searching
+ // from there but instead from the beginning of the document. Otherwise, the
+ // content that appears before the retargetdocumentfocus element will never
+ // get checked as it will be skipped when the focus is retargetted to it.
+ if (forDocumentNavigation && doc->IsXULDocument()) {
+ nsAutoString retarget;
+
+ if (rootContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::retargetdocumentfocus, retarget)) {
+ nsIContent* retargetElement = doc->GetElementById(retarget);
+ // The common case here is the urlbar where focus is on the anonymous
+ // input inside the textbox, but the retargetdocumentfocus attribute
+ // refers to the textbox. The Contains check will return false and the
+ // ContentIsDescendantOf check will return true in this case.
+ if (retargetElement && (retargetElement == startContent ||
+ (!retargetElement->Contains(startContent) &&
+ nsContentUtils::ContentIsDescendantOf(startContent, retargetElement)))) {
+ startContent = rootContent;
+ }
+ }
+ }
+
+ NS_ASSERTION(startContent, "starting content not set");
+
+ // keep a reference to the starting content. If we find that again, it means
+ // we've iterated around completely and we don't want to adjust the focus.
+ // The skipOriginalContentCheck will be set to true only for the first time
+ // GetNextTabbableContent is called. This ensures that we don't break out
+ // when nothing is focused to start with. Specifically,
+ // GetNextTabbableContent first checks the root content -- which happens to
+ // be the same as the start content -- when nothing is focused and tabbing
+ // forward. Without skipOriginalContentCheck set to true, we'd end up
+ // returning right away and focusing nothing. Luckily, GetNextTabbableContent
+ // will never wrap around on its own, and can only return the original
+ // content when it is called a second time or later.
+ bool skipOriginalContentCheck = true;
+ nsIContent* originalStartContent = startContent;
+
+ LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
+ LOGFOCUSNAVIGATION((" Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
+ forward, tabIndex, ignoreTabIndex, forDocumentNavigation));
+
+ while (doc) {
+ if (doNavigation) {
+ nsCOMPtr<nsIContent> nextFocus;
+ nsresult rv = GetNextTabbableContent(presShell, rootContent,
+ skipOriginalContentCheck ? nullptr : originalStartContent,
+ startContent, forward,
+ tabIndex, ignoreTabIndex,
+ forDocumentNavigation,
+ getter_AddRefs(nextFocus));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ // Navigation was redirected to a child process, so just return.
+ return NS_OK;
+ }
+
+ // found a content node to focus.
+ if (nextFocus) {
+ LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
+
+ // as long as the found node was not the same as the starting node,
+ // set it as the return value. For document navigation, we can return
+ // the same element in case there is only one content node that could
+ // be returned, for example, in a child process document.
+ if (nextFocus != originalStartContent || forDocumentNavigation) {
+ nextFocus.forget(aNextContent);
+ }
+ return NS_OK;
+ }
+
+ if (popupFrame && !forDocumentNavigation) {
+ // in a popup, so start again from the beginning of the popup. However,
+ // if we already started at the beginning, then there isn't anything to
+ // focus, so just return
+ if (startContent != rootContent) {
+ startContent = rootContent;
+ tabIndex = forward ? 1 : 0;
+ continue;
+ }
+ return NS_OK;
+ }
+ }
+
+ doNavigation = true;
+ skipOriginalContentCheck = forDocumentNavigation;
+ ignoreTabIndex = false;
+
+ if (aNoParentTraversal) {
+ if (startContent == rootContent)
+ return NS_OK;
+
+ startContent = rootContent;
+ tabIndex = forward ? 1 : 0;
+ continue;
+ }
+
+ // Reached the beginning or end of the document. Next, navigate up to the
+ // parent document and try again.
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
+ NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ // Get the frame element this window is inside and, from that, get the
+ // parent document and presshell. If there is no enclosing frame element,
+ // then this is a top-level, embedded or remote window.
+ startContent = piWindow->GetFrameElementInternal();
+ if (startContent) {
+ doc = startContent->GetComposedDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ rootContent = doc->GetRootElement();
+ presShell = doc->GetShell();
+
+ // We can focus the root element now that we have moved to another document.
+ mayFocusRoot = true;
+
+ nsIFrame* frame = startContent->GetPrimaryFrame();
+ if (!frame) {
+ return NS_OK;
+ }
+
+ frame->IsFocusable(&tabIndex, 0);
+ if (tabIndex < 0) {
+ tabIndex = 1;
+ ignoreTabIndex = true;
+ }
+
+ // if the frame is inside a popup, make sure to scan only within the
+ // popup. This handles the situation of tabbing amongst elements
+ // inside an iframe which is itself inside a popup. Otherwise,
+ // navigation would move outside the popup when tabbing outside the
+ // iframe.
+ if (!forDocumentNavigation) {
+ popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
+ nsGkAtoms::menuPopupFrame);
+ if (popupFrame) {
+ rootContent = popupFrame->GetContent();
+ NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
+ }
+ }
+ }
+ else {
+ // There is no parent, so call the tree owner. This will tell the
+ // embedder or parent process that it should take the focus.
+ bool tookFocus;
+ docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
+ // If the tree owner took the focus, blur the current content.
+ if (tookFocus) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
+ if (window->GetFocusedNode() == mFocusedContent)
+ Blur(mFocusedWindow, nullptr, true, true);
+ else
+ window->SetFocusedNode(nullptr);
+ return NS_OK;
+ }
+
+ // If we have reached the end of the top-level document, focus the
+ // first element in the top-level document. This should always happen
+ // when navigating by document forwards but when navigating backwards,
+ // only do this if we started in another document or within a popup frame.
+ // If the focus started in this window outside a popup however, we should
+ // continue by looping around to the end again.
+ if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
+ // HTML content documents can have their root element focused (a focus
+ // ring appears around the entire content area frame). This root
+ // appears in the tab order before all of the elements in the document.
+ // Chrome documents however cannot be focused directly, so instead we
+ // focus the first focusable element within the window.
+ // For example, the urlbar.
+ nsIContent* root = GetRootForFocus(piWindow, doc, true, true);
+ return FocusFirst(root, aNextContent);
+ }
+
+ // Once we have hit the top-level and have iterated to the end again, we
+ // just want to break out next time we hit this spot to prevent infinite
+ // iteration.
+ mayFocusRoot = true;
+
+ // reset the tab index and start again from the beginning or end
+ startContent = rootContent;
+ tabIndex = forward ? 1 : 0;
+ }
+
+ // wrapped all the way around and didn't find anything to move the focus
+ // to, so just break out
+ if (startContent == originalStartContent)
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
+ nsIContent* aRootContent,
+ nsIContent* aOriginalStartContent,
+ nsIContent* aStartContent,
+ bool aForward,
+ int32_t aCurrentTabIndex,
+ bool aIgnoreTabIndex,
+ bool aForDocumentNavigation,
+ nsIContent** aResultContent)
+{
+ *aResultContent = nullptr;
+
+ nsCOMPtr<nsIContent> startContent = aStartContent;
+ if (!startContent)
+ return NS_OK;
+
+ LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
+ LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+
+ bool getNextFrame = true;
+ nsCOMPtr<nsIContent> iterStartContent = aStartContent;
+ while (1) {
+ nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
+ // if there is no frame, look for another content node that has a frame
+ if (!startFrame) {
+ // if the root content doesn't have a frame, just return
+ if (iterStartContent == aRootContent)
+ return NS_OK;
+
+ // look for the next or previous content node in tree order
+ iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent();
+ // we've already skipped over the initial focused content, so we
+ // don't want to traverse frames.
+ getNextFrame = false;
+ if (iterStartContent)
+ continue;
+
+ // otherwise, as a last attempt, just look at the root content
+ iterStartContent = aRootContent;
+ continue;
+ }
+
+ // For tab navigation, pass false for aSkipPopupChecks so that we don't
+ // iterate into or out of a popup. For document naviation pass true to
+ // ignore these boundaries.
+ nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+ nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
+ presContext, startFrame,
+ ePreOrder,
+ false, // aVisual
+ false, // aLockInScrollView
+ true, // aFollowOOFs
+ aForDocumentNavigation // aSkipPopupChecks
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iterStartContent == aRootContent) {
+ if (!aForward) {
+ frameTraversal->Last();
+ } else if (aRootContent->IsFocusable()) {
+ frameTraversal->Next();
+ }
+ }
+ else if (getNextFrame &&
+ (!iterStartContent ||
+ !iterStartContent->IsHTMLElement(nsGkAtoms::area))) {
+ // Need to do special check in case we're in an imagemap which has multiple
+ // content nodes per frame, so don't skip over the starting frame.
+ if (aForward)
+ frameTraversal->Next();
+ else
+ frameTraversal->Prev();
+ }
+
+ // Walk frames to find something tabbable matching mCurrentTabIndex
+ nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ while (frame) {
+ nsIContent* currentContent = frame->GetContent();
+
+ // For document navigation, check if this element is an open panel. Since
+ // panels aren't focusable (tabIndex would be -1), we'll just assume that
+ // for document navigation, the tabIndex is 0.
+ if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
+ currentContent->IsXULElement(nsGkAtoms::panel)) {
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
+ // Check if the panel is open. Closed panels are ignored since you can't
+ // focus anything in them.
+ if (popupFrame && popupFrame->IsOpen()) {
+ // When moving backward, skip the popup we started in otherwise it
+ // will be selected again.
+ bool validPopup = true;
+ if (!aForward) {
+ nsIContent* content = aStartContent;
+ while (content) {
+ if (content == currentContent) {
+ validPopup = false;
+ break;
+ }
+
+ content = content->GetParent();
+ }
+ }
+
+ if (validPopup) {
+ // Since a panel isn't focusable itself, find the first focusable
+ // content within the popup. If there isn't any focusable content
+ // in the popup, skip this popup and continue iterating through the
+ // frames. We pass the panel itself (currentContent) as the starting
+ // and root content, so that we only find content within the panel.
+ // Note also that we pass false for aForDocumentNavigation since we
+ // want to locate the first content, not the first document.
+ rv = GetNextTabbableContent(aPresShell, currentContent,
+ nullptr, currentContent,
+ true, 1, false, false,
+ aResultContent);
+ if (NS_SUCCEEDED(rv) && *aResultContent) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ // TabIndex not set defaults to 0 for form elements, anchors and other
+ // elements that are normally focusable. Tabindex defaults to -1
+ // for elements that are not normally focusable.
+ // The returned computed tabindex from IsFocusable() is as follows:
+ // < 0 not tabbable at all
+ // == 0 in normal tab order (last after positive tabindexed items)
+ // > 0 can be tabbed to in the order specified by this value
+ int32_t tabIndex;
+ frame->IsFocusable(&tabIndex, 0);
+
+ LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
+ LOGFOCUSNAVIGATION((" with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
+
+ if (tabIndex >= 0) {
+ NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
+ if (!aForDocumentNavigation &&
+ currentContent->IsHTMLElement(nsGkAtoms::img) &&
+ currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
+ // This is an image with a map. Image map areas are not traversed by
+ // nsIFrameTraversal so look for the next or previous area element.
+ nsIContent *areaContent =
+ GetNextTabbableMapArea(aForward, aCurrentTabIndex,
+ currentContent, iterStartContent);
+ if (areaContent) {
+ NS_ADDREF(*aResultContent = areaContent);
+ return NS_OK;
+ }
+ }
+ else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
+ // break out if we've wrapped around to the start again.
+ if (aOriginalStartContent && currentContent == aOriginalStartContent) {
+ NS_ADDREF(*aResultContent = currentContent);
+ return NS_OK;
+ }
+
+ // If this is a remote child browser, call NavigateDocument to have
+ // the child process continue the navigation. Return a special error
+ // code to have the caller return early. If the child ends up not
+ // being focusable in some way, the child process will call back
+ // into document navigation again by calling MoveFocus.
+ TabParent* remote = TabParent::GetFrom(currentContent);
+ if (remote) {
+ remote->NavigateByKey(aForward, aForDocumentNavigation);
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Next, for document navigation, check if this a non-remote child document.
+ bool checkSubDocument = true;
+ if (aForDocumentNavigation) {
+ nsIContent* docRoot = GetRootForChildDocument(currentContent);
+ if (docRoot) {
+ // If GetRootForChildDocument returned something then call
+ // FocusFirst to find the root or first element to focus within
+ // the child document. If this is a frameset though, skip this and
+ // fall through to the checkSubDocument block below to iterate into
+ // the frameset's frames and locate the first focusable frame.
+ if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
+ return FocusFirst(docRoot, aResultContent);
+ }
+ } else {
+ // Set checkSubDocument to false, as this was neither a frame
+ // type element or a child document that was focusable.
+ checkSubDocument = false;
+ }
+ }
+
+ if (checkSubDocument) {
+ // found a node with a matching tab index. Check if it is a child
+ // frame. If so, navigate into the child frame instead.
+ nsIDocument* doc = currentContent->GetComposedDoc();
+ NS_ASSERTION(doc, "content not in document");
+ nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
+ if (subdoc && !subdoc->EventHandlingSuppressed()) {
+ if (aForward) {
+ // when tabbing forward into a frame, return the root
+ // frame so that the canvas becomes focused.
+ nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow();
+ if (subframe) {
+ *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
+ if (*aResultContent) {
+ NS_ADDREF(*aResultContent);
+ return NS_OK;
+ }
+ }
+ }
+ Element* rootElement = subdoc->GetRootElement();
+ nsIPresShell* subShell = subdoc->GetShell();
+ if (rootElement && subShell) {
+ rv = GetNextTabbableContent(subShell, rootElement,
+ aOriginalStartContent, rootElement,
+ aForward, (aForward ? 1 : 0),
+ false, aForDocumentNavigation, aResultContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aResultContent)
+ return NS_OK;
+ }
+ }
+ // otherwise, use this as the next content node to tab to, unless
+ // this was the element we started on. This would happen for
+ // instance on an element with child frames, where frame navigation
+ // could return the original element again. In that case, just skip
+ // it. Also, if the next content node is the root content, then
+ // return it. This latter case would happen only if someone made a
+ // popup focusable.
+ // Also, when going backwards, check to ensure that the focus
+ // wouldn't be redirected. Otherwise, for example, when an input in
+ // a textbox is focused, the enclosing textbox would be found and
+ // the same inner input would be returned again.
+ else if (currentContent == aRootContent ||
+ (currentContent != startContent &&
+ (aForward || !GetRedirectedFocus(currentContent)))) {
+ NS_ADDREF(*aResultContent = currentContent);
+ return NS_OK;
+ }
+ }
+ }
+ }
+ else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
+ // not focusable, so return if we have wrapped around to the original
+ // content. This is necessary in case the original starting content was
+ // not focusable.
+ NS_ADDREF(*aResultContent = currentContent);
+ return NS_OK;
+ }
+
+ // Move to the next or previous frame, but ignore continuation frames
+ // since only the first frame should be involved in focusability.
+ // Otherwise, a loop will occur in the following example:
+ // <span tabindex="1">...<a/><a/>...</span>
+ // where the text wraps onto multiple lines. Tabbing from the second
+ // link can find one of the span's continuation frames between the link
+ // and the end of the span, and the span would end up getting focused
+ // again.
+ do {
+ if (aForward)
+ frameTraversal->Next();
+ else
+ frameTraversal->Prev();
+ frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ } while (frame && frame->GetPrevContinuation());
+ }
+
+ // If already at lowest priority tab (0), end search completely.
+ // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
+ if (aCurrentTabIndex == (aForward ? 0 : 1)) {
+ // if going backwards, the canvas should be focused once the beginning
+ // has been reached, so get the root element.
+ if (!aForward) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetCurrentWindow(aRootContent);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIContent> docRoot =
+ GetRootForFocus(window, aRootContent->GetComposedDoc(), false, true);
+ FocusFirst(docRoot, aResultContent);
+ }
+ break;
+ }
+
+ // continue looking for next highest priority tabindex
+ aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
+ startContent = iterStartContent = aRootContent;
+ }
+
+ return NS_OK;
+}
+
+nsIContent*
+nsFocusManager::GetNextTabbableMapArea(bool aForward,
+ int32_t aCurrentTabIndex,
+ nsIContent* aImageContent,
+ nsIContent* aStartContent)
+{
+ nsAutoString useMap;
+ aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
+
+ nsCOMPtr<nsIDocument> doc = aImageContent->GetComposedDoc();
+ if (doc) {
+ nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap);
+ if (!mapContent)
+ return nullptr;
+ uint32_t count = mapContent->GetChildCount();
+ // First see if the the start content is in this map
+
+ int32_t index = mapContent->IndexOf(aStartContent);
+ int32_t tabIndex;
+ if (index < 0 || (aStartContent->IsFocusable(&tabIndex) &&
+ tabIndex != aCurrentTabIndex)) {
+ // If aStartContent is in this map we must start iterating past it.
+ // We skip the case where aStartContent has tabindex == aStartContent
+ // since the next tab ordered element might be before it
+ // (or after for backwards) in the child list.
+ index = aForward ? -1 : (int32_t)count;
+ }
+
+ // GetChildAt will return nullptr if our index < 0 or index >= count
+ nsCOMPtr<nsIContent> areaContent;
+ while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nullptr) {
+ if (areaContent->IsFocusable(&tabIndex) && tabIndex == aCurrentTabIndex) {
+ return areaContent;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+int32_t
+nsFocusManager::GetNextTabIndex(nsIContent* aParent,
+ int32_t aCurrentTabIndex,
+ bool aForward)
+{
+ int32_t tabIndex, childTabIndex;
+
+ if (aForward) {
+ tabIndex = 0;
+ for (nsIContent* child = aParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
+ if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
+ tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
+ }
+
+ nsAutoString tabIndexStr;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
+ nsresult ec;
+ int32_t val = tabIndexStr.ToInteger(&ec);
+ if (NS_SUCCEEDED (ec) && val > aCurrentTabIndex && val != tabIndex) {
+ tabIndex = (tabIndex == 0 || val < tabIndex) ? val : tabIndex;
+ }
+ }
+ }
+ else { /* !aForward */
+ tabIndex = 1;
+ for (nsIContent* child = aParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
+ if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
+ (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
+ tabIndex = childTabIndex;
+ }
+
+ nsAutoString tabIndexStr;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
+ nsresult ec;
+ int32_t val = tabIndexStr.ToInteger(&ec);
+ if (NS_SUCCEEDED (ec)) {
+ if ((aCurrentTabIndex == 0 && val > tabIndex) ||
+ (val < aCurrentTabIndex && val > tabIndex) ) {
+ tabIndex = val;
+ }
+ }
+ }
+ }
+
+ return tabIndex;
+}
+
+nsresult
+nsFocusManager::FocusFirst(nsIContent* aRootContent, nsIContent** aNextContent)
+{
+ if (!aRootContent) {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = aRootContent->GetComposedDoc();
+ if (doc) {
+ if (doc->IsXULDocument()) {
+ // If the redirectdocumentfocus attribute is set, redirect the focus to a
+ // specific element. This is primarily used to retarget the focus to the
+ // urlbar during document navigation.
+ nsAutoString retarget;
+
+ if (aRootContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::retargetdocumentfocus, retarget)) {
+ nsCOMPtr<Element> element = doc->GetElementById(retarget);
+ nsCOMPtr<nsIContent> retargetElement =
+ CheckIfFocusable(element, 0);
+ if (retargetElement) {
+ retargetElement.forget(aNextContent);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ // If the found content is in a chrome shell, navigate forward one
+ // tabbable item so that the first item is focused. Note that we
+ // always go forward and not back here.
+ nsIPresShell* presShell = doc->GetShell();
+ if (presShell) {
+ return GetNextTabbableContent(presShell, aRootContent,
+ nullptr, aRootContent,
+ true, 1, false, false,
+ aNextContent);
+ }
+ }
+ }
+
+ NS_ADDREF(*aNextContent = aRootContent);
+ return NS_OK;
+}
+
+nsIContent*
+nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow,
+ nsIDocument* aDocument,
+ bool aForDocumentNavigation,
+ bool aCheckVisibility)
+{
+ if (!aForDocumentNavigation) {
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return nullptr;
+ }
+ }
+
+ if (aCheckVisibility && !IsWindowVisible(aWindow))
+ return nullptr;
+
+ // If the body is contenteditable, use the editor's root element rather than
+ // the actual root element.
+ nsCOMPtr<nsIContent> rootElement =
+ nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
+ if (!rootElement || !rootElement->GetPrimaryFrame()) {
+ rootElement = aDocument->GetRootElement();
+ if (!rootElement) {
+ return nullptr;
+ }
+ }
+
+ if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
+ return nullptr;
+ }
+
+ // Finally, check if this is a frameset
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
+ if (htmlDoc) {
+ nsIContent* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
+ if (htmlChild) {
+ // In document navigation mode, return the frameset so that navigation
+ // descends into the child frames.
+ return aForDocumentNavigation ? htmlChild : nullptr;
+ }
+ }
+
+ return rootElement;
+}
+
+nsIContent*
+nsFocusManager::GetRootForChildDocument(nsIContent* aContent)
+{
+ // Check for elements that represent child documents, that is, browsers,
+ // editors or frames from a frameset. We don't include iframes since we
+ // consider them to be an integral part of the same window or page.
+ if (!aContent ||
+ !(aContent->IsXULElement(nsGkAtoms::browser) ||
+ aContent->IsXULElement(nsGkAtoms::editor) ||
+ aContent->IsHTMLElement(nsGkAtoms::frame))) {
+ return nullptr;
+ }
+
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+ if (!subdoc || subdoc->EventHandlingSuppressed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow();
+ return GetRootForFocus(window, subdoc, true, true);
+}
+
+void
+nsFocusManager::GetFocusInSelection(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aStartSelection,
+ nsIContent* aEndSelection,
+ nsIContent** aFocusedContent)
+{
+ *aFocusedContent = nullptr;
+
+ nsCOMPtr<nsIContent> testContent = aStartSelection;
+ nsCOMPtr<nsIContent> nextTestContent = aEndSelection;
+
+ nsCOMPtr<nsIContent> currentFocus = aWindow->GetFocusedNode();
+
+ // We now have the correct start node in selectionContent!
+ // Search for focusable elements, starting with selectionContent
+
+ // Method #1: Keep going up while we look - an ancestor might be focusable
+ // We could end the loop earlier, such as when we're no longer
+ // in the same frame, by comparing selectionContent->GetPrimaryFrame()
+ // with a variable holding the starting selectionContent
+ while (testContent) {
+ // Keep testing while selectionContent is equal to something,
+ // eventually we'll run out of ancestors
+
+ nsCOMPtr<nsIURI> uri;
+ if (testContent == currentFocus ||
+ testContent->IsLink(getter_AddRefs(uri))) {
+ testContent.forget(aFocusedContent);
+ return;
+ }
+
+ // Get the parent
+ testContent = testContent->GetParent();
+
+ if (!testContent) {
+ // We run this loop again, checking the ancestor chain of the selection's end point
+ testContent = nextTestContent;
+ nextTestContent = nullptr;
+ }
+ }
+
+ // We couldn't find an anchor that was an ancestor of the selection start
+ // Method #2: look for anchor in selection's primary range (depth first search)
+
+ // Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
+ nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
+ nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
+ nsCOMPtr<nsIDOMNode> testNode;
+
+ do {
+ testContent = do_QueryInterface(selectionNode);
+
+ // We're looking for any focusable link that could be part of the
+ // main document's selection.
+ nsCOMPtr<nsIURI> uri;
+ if (testContent == currentFocus ||
+ testContent->IsLink(getter_AddRefs(uri))) {
+ testContent.forget(aFocusedContent);
+ return;
+ }
+
+ selectionNode->GetFirstChild(getter_AddRefs(testNode));
+ if (testNode) {
+ selectionNode = testNode;
+ continue;
+ }
+
+ if (selectionNode == endSelectionNode)
+ break;
+ selectionNode->GetNextSibling(getter_AddRefs(testNode));
+ if (testNode) {
+ selectionNode = testNode;
+ continue;
+ }
+
+ do {
+ selectionNode->GetParentNode(getter_AddRefs(testNode));
+ if (!testNode || testNode == endSelectionNode) {
+ selectionNode = nullptr;
+ break;
+ }
+ testNode->GetNextSibling(getter_AddRefs(selectionNode));
+ if (selectionNode)
+ break;
+ selectionNode = testNode;
+ } while (true);
+ }
+ while (selectionNode && selectionNode != endSelectionNode);
+}
+
+class PointerUnlocker : public Runnable
+{
+public:
+ PointerUnlocker()
+ {
+ MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
+ PointerUnlocker::sActiveUnlocker = this;
+ }
+
+ ~PointerUnlocker()
+ {
+ if (PointerUnlocker::sActiveUnlocker == this) {
+ PointerUnlocker::sActiveUnlocker = nullptr;
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (PointerUnlocker::sActiveUnlocker == this) {
+ PointerUnlocker::sActiveUnlocker = nullptr;
+ }
+ NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
+ nsPIDOMWindowOuter* focused =
+ nsFocusManager::GetFocusManager()->GetFocusedWindow();
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ if (pointerLockedDoc &&
+ !nsContentUtils::IsInPointerLockContext(focused)) {
+ nsIDocument::UnlockPointer();
+ }
+ return NS_OK;
+ }
+
+ static PointerUnlocker* sActiveUnlocker;
+};
+
+PointerUnlocker*
+PointerUnlocker::sActiveUnlocker = nullptr;
+
+void
+nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow)
+{
+ if (!PointerUnlocker::sActiveUnlocker &&
+ nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
+ !nsContentUtils::IsInPointerLockContext(aWindow)) {
+ nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
+ NS_DispatchToCurrentThread(runnable);
+ }
+ mFocusedWindow = aWindow;
+}
+
+void
+nsFocusManager::MarkUncollectableForCCGeneration(uint32_t aGeneration)
+{
+ if (!sInstance) {
+ return;
+ }
+
+ if (sInstance->mActiveWindow) {
+ sInstance->mActiveWindow->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFocusedWindow) {
+ sInstance->mFocusedWindow->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mWindowBeingLowered) {
+ sInstance->mWindowBeingLowered->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFocusedContent) {
+ sInstance->mFocusedContent->OwnerDoc()->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFirstBlurEvent) {
+ sInstance->mFirstBlurEvent->OwnerDoc()->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFirstFocusEvent) {
+ sInstance->mFirstFocusEvent->OwnerDoc()->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mMouseButtonEventHandlingDocument) {
+ sInstance->mMouseButtonEventHandlingDocument->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+}
+
+nsresult
+NS_NewFocusManager(nsIFocusManager** aResult)
+{
+ NS_IF_ADDREF(*aResult = nsFocusManager::GetFocusManager());
+ return NS_OK;
+}