FocusTarget.cpp (8137B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/layers/FocusTarget.h" 8 #include "mozilla/dom/BrowserBridgeChild.h" // for BrowserBridgeChild 9 #include "mozilla/dom/EventTarget.h" // for EventTarget 10 #include "mozilla/dom/RemoteBrowser.h" // For RemoteBrowser 11 #include "mozilla/EventDispatcher.h" // for EventDispatcher 12 #include "mozilla/PresShell.h" // For PresShell 13 #include "mozilla/StaticPrefs_apz.h" 14 #include "nsIContentInlines.h" // for nsINode::IsEditable() 15 #include "nsLayoutUtils.h" // for nsLayoutUtils 16 17 static mozilla::LazyLogModule sApzFtgLog("apz.focustarget"); 18 #define FT_LOG(...) MOZ_LOG(sApzFtgLog, LogLevel::Debug, (__VA_ARGS__)) 19 20 using namespace mozilla::dom; 21 using namespace mozilla::layout; 22 23 namespace mozilla { 24 namespace layers { 25 26 static PresShell* GetRetargetEventPresShell(PresShell* aRootPresShell) { 27 MOZ_ASSERT(aRootPresShell); 28 29 // Use the last focused window in this PresShell and its 30 // associated PresShell 31 nsCOMPtr<nsPIDOMWindowOuter> window = 32 aRootPresShell->GetFocusedDOMWindowInOurWindow(); 33 if (!window) { 34 return nullptr; 35 } 36 37 RefPtr<Document> retargetEventDoc = window->GetExtantDoc(); 38 if (!retargetEventDoc) { 39 return nullptr; 40 } 41 42 return retargetEventDoc->GetPresShell(); 43 } 44 45 // _BOUNDARY because Dispatch() with `targets` must not handle the event. 46 MOZ_CAN_RUN_SCRIPT_BOUNDARY static bool HasListenersForKeyEvents( 47 nsIContent* aContent) { 48 if (!aContent) { 49 return false; 50 } 51 52 WidgetEvent event(true, eVoidEvent); 53 nsTArray<EventTarget*> targets; 54 nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr, 55 nullptr, nullptr, &targets); 56 NS_ENSURE_SUCCESS(rv, false); 57 for (size_t i = 0; i < targets.Length(); i++) { 58 if (targets[i]->HasNonSystemGroupListenersForUntrustedKeyEvents()) { 59 return true; 60 } 61 } 62 return false; 63 } 64 65 // _BOUNDARY because Dispatch() with `targets` must not handle the event. 66 MOZ_CAN_RUN_SCRIPT_BOUNDARY static bool HasListenersForNonPassiveKeyEvents( 67 nsIContent* aContent) { 68 if (!aContent) { 69 return false; 70 } 71 72 WidgetEvent event(true, eVoidEvent); 73 nsTArray<EventTarget*> targets; 74 nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr, 75 nullptr, nullptr, &targets); 76 NS_ENSURE_SUCCESS(rv, false); 77 for (size_t i = 0; i < targets.Length(); i++) { 78 if (targets[i] 79 ->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()) { 80 return true; 81 } 82 } 83 return false; 84 } 85 86 static bool IsEditableNode(nsINode* aNode) { 87 return aNode && aNode->IsEditable(); 88 } 89 90 FocusTarget::FocusTarget() 91 : mSequenceNumber(0), 92 mFocusHasKeyEventListeners(false), 93 mData(AsVariant(NoFocusTarget())) {} 94 95 FocusTarget::FocusTarget(PresShell* aRootPresShell, 96 uint64_t aFocusSequenceNumber) 97 : mSequenceNumber(aFocusSequenceNumber), 98 mFocusHasKeyEventListeners(false), 99 mData(AsVariant(NoFocusTarget())) { 100 MOZ_ASSERT(aRootPresShell); 101 MOZ_ASSERT(NS_IsMainThread()); 102 103 // Key events can be retargeted to a child PresShell when there is an iframe 104 RefPtr<PresShell> presShell = GetRetargetEventPresShell(aRootPresShell); 105 106 if (!presShell) { 107 FT_LOG("Creating nil target with seq=%" PRIu64 108 " (can't find retargeted presshell)\n", 109 aFocusSequenceNumber); 110 111 return; 112 } 113 114 RefPtr<Document> document = presShell->GetDocument(); 115 if (!document) { 116 FT_LOG("Creating nil target with seq=%" PRIu64 " (no document)\n", 117 aFocusSequenceNumber); 118 119 return; 120 } 121 122 // Find the focused content and use it to determine whether there are key 123 // event listeners or whether key events will be targeted at a different 124 // process through a remote browser. 125 nsCOMPtr<nsIContent> focusedContent = 126 presShell->GetFocusedContentInOurWindow(); 127 nsCOMPtr<nsIContent> keyEventTarget = focusedContent; 128 129 // If there is no focused element then event dispatch goes to the body of 130 // the page if it exists or the root element. 131 if (!keyEventTarget) { 132 keyEventTarget = document->GetUnfocusedKeyEventTarget(); 133 } 134 135 // Check if there are key event listeners that could prevent default or change 136 // the focus or selection of the page. 137 if (StaticPrefs::apz_keyboard_passive_listeners()) { 138 mFocusHasKeyEventListeners = 139 HasListenersForNonPassiveKeyEvents(keyEventTarget.get()); 140 } else { 141 mFocusHasKeyEventListeners = HasListenersForKeyEvents(keyEventTarget.get()); 142 } 143 144 // Check if the key event target is content editable or if the document 145 // is in design mode. 146 if (IsEditableNode(keyEventTarget) || IsEditableNode(document)) { 147 FT_LOG("Creating nil target with seq=%" PRIu64 148 ", kl=%d (disabling for editable node)\n", 149 aFocusSequenceNumber, static_cast<int>(mFocusHasKeyEventListeners)); 150 151 return; 152 } 153 154 // Check if the key event target is a remote browser 155 if (RemoteBrowser* remoteBrowser = RemoteBrowser::GetFrom(keyEventTarget)) { 156 LayersId layersId = remoteBrowser->GetLayersId(); 157 158 // The globally focused element for scrolling is in a remote layer tree 159 if (layersId.IsValid()) { 160 FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64 161 "\n", 162 aFocusSequenceNumber, mFocusHasKeyEventListeners, layersId.mId); 163 164 mData = AsVariant<LayersId>(std::move(layersId)); 165 return; 166 } 167 168 FT_LOG("Creating nil target with seq=%" PRIu64 169 ", kl=%d (remote browser missing layers id)\n", 170 aFocusSequenceNumber, mFocusHasKeyEventListeners); 171 172 return; 173 } 174 175 // The content to scroll is either the focused element or the focus node of 176 // the selection. It's difficult to determine if an element is an interactive 177 // element requiring async keyboard scrolling to be disabled. So we only 178 // allow async key scrolling based on the selection, which doesn't have 179 // this problem and is more common. 180 if (focusedContent) { 181 FT_LOG("Creating nil target with seq=%" PRIu64 182 ", kl=%d (disabling for focusing an element)\n", 183 aFocusSequenceNumber, mFocusHasKeyEventListeners); 184 185 return; 186 } 187 188 nsCOMPtr<nsIContent> selectedContent = 189 presShell->GetSelectedContentForScrolling(); 190 191 // Gather the scroll container frames that would be scrolled in each direction 192 // for this scroll target 193 ScrollContainerFrame* horizontal = 194 presShell->GetScrollContainerFrameToScrollForContent( 195 selectedContent.get(), HorizontalScrollDirection); 196 ScrollContainerFrame* vertical = 197 presShell->GetScrollContainerFrameToScrollForContent( 198 selectedContent.get(), VerticalScrollDirection); 199 200 // We might have the globally focused element for scrolling. Gather a ViewID 201 // for the horizontal and vertical scroll targets of this element. 202 ScrollTargets target; 203 target.mHorizontal = nsLayoutUtils::FindIDForScrollContainerFrame(horizontal); 204 target.mVertical = nsLayoutUtils::FindIDForScrollContainerFrame(vertical); 205 mData = AsVariant(target); 206 207 FT_LOG("Creating scroll target with seq=%" PRIu64 ", kl=%d, h=%" PRIu64 208 ", v=%" PRIu64 "\n", 209 aFocusSequenceNumber, mFocusHasKeyEventListeners, target.mHorizontal, 210 target.mVertical); 211 } 212 213 bool FocusTarget::operator==(const FocusTarget& aRhs) const { 214 return mSequenceNumber == aRhs.mSequenceNumber && 215 mFocusHasKeyEventListeners == aRhs.mFocusHasKeyEventListeners && 216 mData == aRhs.mData; 217 } 218 219 const char* FocusTarget::Type() const { 220 if (mData.is<LayersId>()) { 221 return "LayersId"; 222 } 223 if (mData.is<ScrollTargets>()) { 224 return "ScrollTargets"; 225 } 226 if (mData.is<NoFocusTarget>()) { 227 return "NoFocusTarget"; 228 } 229 return "<unknown>"; 230 } 231 232 } // namespace layers 233 } // namespace mozilla