/* -*- 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 window = aRootPresShell->GetFocusedDOMWindowInOurWindow(); if (!window) { return nullptr; } RefPtr 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 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 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 = GetRetargetEventPresShell(aRootPresShell); if (!presShell) { FT_LOG("Creating nil target with seq=%" PRIu64 " (can't find retargeted presshell)\n", aFocusSequenceNumber); return; } RefPtr 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 focusedContent = presShell->GetFocusedContentInOurWindow(); nsCOMPtr 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(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(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 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()) { return "LayersId"; } if (mData.is()) { return "ScrollTargets"; } if (mData.is()) { return "NoFocusTarget"; } return ""; } } // namespace layers } // namespace mozilla