diff options
Diffstat (limited to 'gfx/layers/apz/src/FocusTarget.cpp')
-rw-r--r-- | gfx/layers/apz/src/FocusTarget.cpp | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/FocusTarget.cpp b/gfx/layers/apz/src/FocusTarget.cpp new file mode 100644 index 0000000000..f1f6463e5c --- /dev/null +++ b/gfx/layers/apz/src/FocusTarget.cpp @@ -0,0 +1,233 @@ +/* -*- 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/layers/FocusTarget.h" +#include "mozilla/dom/BrowserBridgeChild.h" // for BrowserBridgeChild +#include "mozilla/dom/EventTarget.h" // for EventTarget +#include "mozilla/dom/RemoteBrowser.h" // For RemoteBrowser +#include "mozilla/EventDispatcher.h" // for EventDispatcher +#include "mozilla/PresShell.h" // For PresShell +#include "mozilla/StaticPrefs_apz.h" +#include "nsIContentInlines.h" // for nsINode::IsEditable() +#include "nsLayoutUtils.h" // for nsLayoutUtils + +static mozilla::LazyLogModule sApzFtgLog("apz.focustarget"); +#define FT_LOG(...) MOZ_LOG(sApzFtgLog, LogLevel::Debug, (__VA_ARGS__)) + +using namespace mozilla::dom; +using namespace mozilla::layout; + +namespace mozilla { +namespace layers { + +static PresShell* GetRetargetEventPresShell(PresShell* aRootPresShell) { + MOZ_ASSERT(aRootPresShell); + + // Use the last focused window in this PresShell and its + // associated PresShell + nsCOMPtr<nsPIDOMWindowOuter> window = + aRootPresShell->GetFocusedDOMWindowInOurWindow(); + if (!window) { + return nullptr; + } + + RefPtr<Document> retargetEventDoc = window->GetExtantDoc(); + if (!retargetEventDoc) { + return nullptr; + } + + return retargetEventDoc->GetPresShell(); +} + +// _BOUNDARY because Dispatch() with `targets` must not handle the event. +MOZ_CAN_RUN_SCRIPT_BOUNDARY static bool HasListenersForKeyEvents( + nsIContent* aContent) { + if (!aContent) { + return false; + } + + WidgetEvent event(true, eVoidEvent); + nsTArray<EventTarget*> targets; + nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr, + nullptr, nullptr, &targets); + NS_ENSURE_SUCCESS(rv, false); + for (size_t i = 0; i < targets.Length(); i++) { + if (targets[i]->HasNonSystemGroupListenersForUntrustedKeyEvents()) { + return true; + } + } + return false; +} + +// _BOUNDARY because Dispatch() with `targets` must not handle the event. +MOZ_CAN_RUN_SCRIPT_BOUNDARY static bool HasListenersForNonPassiveKeyEvents( + nsIContent* aContent) { + if (!aContent) { + return false; + } + + WidgetEvent event(true, eVoidEvent); + nsTArray<EventTarget*> targets; + nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr, + nullptr, nullptr, &targets); + NS_ENSURE_SUCCESS(rv, false); + for (size_t i = 0; i < targets.Length(); i++) { + if (targets[i] + ->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()) { + return true; + } + } + return false; +} + +static bool IsEditableNode(nsINode* aNode) { + return aNode && aNode->IsEditable(); +} + +FocusTarget::FocusTarget() + : mSequenceNumber(0), + mFocusHasKeyEventListeners(false), + mData(AsVariant(NoFocusTarget())) {} + +FocusTarget::FocusTarget(PresShell* aRootPresShell, + uint64_t aFocusSequenceNumber) + : mSequenceNumber(aFocusSequenceNumber), + mFocusHasKeyEventListeners(false), + mData(AsVariant(NoFocusTarget())) { + MOZ_ASSERT(aRootPresShell); + MOZ_ASSERT(NS_IsMainThread()); + + // Key events can be retargeted to a child PresShell when there is an iframe + RefPtr<PresShell> presShell = GetRetargetEventPresShell(aRootPresShell); + + if (!presShell) { + FT_LOG("Creating nil target with seq=%" PRIu64 + " (can't find retargeted presshell)\n", + aFocusSequenceNumber); + + return; + } + + RefPtr<Document> document = presShell->GetDocument(); + if (!document) { + FT_LOG("Creating nil target with seq=%" PRIu64 " (no document)\n", + aFocusSequenceNumber); + + return; + } + + // Find the focused content and use it to determine whether there are key + // event listeners or whether key events will be targeted at a different + // process through a remote browser. + nsCOMPtr<nsIContent> focusedContent = + presShell->GetFocusedContentInOurWindow(); + nsCOMPtr<nsIContent> keyEventTarget = focusedContent; + + // If there is no focused element then event dispatch goes to the body of + // the page if it exists or the root element. + if (!keyEventTarget) { + keyEventTarget = document->GetUnfocusedKeyEventTarget(); + } + + // Check if there are key event listeners that could prevent default or change + // the focus or selection of the page. + if (StaticPrefs::apz_keyboard_passive_listeners()) { + mFocusHasKeyEventListeners = + HasListenersForNonPassiveKeyEvents(keyEventTarget.get()); + } else { + mFocusHasKeyEventListeners = HasListenersForKeyEvents(keyEventTarget.get()); + } + + // Check if the key event target is content editable or if the document + // is in design mode. + if (IsEditableNode(keyEventTarget) || IsEditableNode(document)) { + FT_LOG("Creating nil target with seq=%" PRIu64 + ", kl=%d (disabling for editable node)\n", + aFocusSequenceNumber, static_cast<int>(mFocusHasKeyEventListeners)); + + return; + } + + // Check if the key event target is a remote browser + if (RemoteBrowser* remoteBrowser = RemoteBrowser::GetFrom(keyEventTarget)) { + LayersId layersId = remoteBrowser->GetLayersId(); + + // The globally focused element for scrolling is in a remote layer tree + if (layersId.IsValid()) { + FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64 + "\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners, layersId.mId); + + mData = AsVariant<LayersId>(std::move(layersId)); + return; + } + + FT_LOG("Creating nil target with seq=%" PRIu64 + ", kl=%d (remote browser missing layers id)\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners); + + return; + } + + // The content to scroll is either the focused element or the focus node of + // the selection. It's difficult to determine if an element is an interactive + // element requiring async keyboard scrolling to be disabled. So we only + // allow async key scrolling based on the selection, which doesn't have + // this problem and is more common. + if (focusedContent) { + FT_LOG("Creating nil target with seq=%" PRIu64 + ", kl=%d (disabling for focusing an element)\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners); + + return; + } + + nsCOMPtr<nsIContent> selectedContent = + presShell->GetSelectedContentForScrolling(); + + // Gather the scrollable frames that would be scrolled in each direction + // for this scroll target + nsIScrollableFrame* horizontal = + presShell->GetScrollableFrameToScrollForContent( + selectedContent.get(), HorizontalScrollDirection); + nsIScrollableFrame* vertical = + presShell->GetScrollableFrameToScrollForContent(selectedContent.get(), + VerticalScrollDirection); + + // We might have the globally focused element for scrolling. Gather a ViewID + // for the horizontal and vertical scroll targets of this element. + ScrollTargets target; + target.mHorizontal = nsLayoutUtils::FindIDForScrollableFrame(horizontal); + target.mVertical = nsLayoutUtils::FindIDForScrollableFrame(vertical); + mData = AsVariant(target); + + FT_LOG("Creating scroll target with seq=%" PRIu64 ", kl=%d, h=%" PRIu64 + ", v=%" PRIu64 "\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners, target.mHorizontal, + target.mVertical); +} + +bool FocusTarget::operator==(const FocusTarget& aRhs) const { + return mSequenceNumber == aRhs.mSequenceNumber && + mFocusHasKeyEventListeners == aRhs.mFocusHasKeyEventListeners && + mData == aRhs.mData; +} + +const char* FocusTarget::Type() const { + if (mData.is<LayersId>()) { + return "LayersId"; + } + if (mData.is<ScrollTargets>()) { + return "ScrollTargets"; + } + if (mData.is<NoFocusTarget>()) { + return "NoFocusTarget"; + } + return "<unknown>"; +} + +} // namespace layers +} // namespace mozilla |