Bug 1351783 part 8 - Gather whether there are key listeners on the focused element. r?kats,smaug draft
authorRyan Hunt <rhunt@eqrion.net>
Mon, 05 Jun 2017 19:22:16 -0500
changeset 599272 5b4db2f72cefe05cedaf9f632ad8898fc4ccdce2
parent 599271 c5e9ea2359feb4da940ea74a337054a10769c2da
child 599273 54fb7209752bbd46ccb8a913788d028242fe93b1
push id65466
push userbmo:rhunt@eqrion.net
push dateThu, 22 Jun 2017 22:16:51 +0000
reviewerskats, smaug
bugs1351783
milestone56.0a1
Bug 1351783 part 8 - Gather whether there are key listeners on the focused element. r?kats,smaug This commit updates FocusTarget to collect whether there are key listeners on the event target chain for the focused element. This is needed because we cannot do async scrolling when this is the case. MozReview-Commit-ID: FhSyF6ffZ4
dom/events/EventListenerManager.cpp
dom/events/EventListenerManager.h
dom/events/EventTarget.cpp
dom/events/EventTarget.h
gfx/layers/apz/src/FocusState.cpp
gfx/layers/apz/src/FocusState.h
gfx/layers/apz/src/FocusTarget.cpp
gfx/layers/apz/src/FocusTarget.h
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1759,16 +1759,33 @@ EventListenerManager::TraceListeners(JST
       mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), aTrc);
     }
     // We might have eWrappedJSListener, but that is the legacy type for
     // JS implemented event listeners, and trickier to handle here.
   }
 }
 
 bool
+EventListenerManager::HasUntrustedOrNonSystemGroupKeyEventListeners()
+{
+  uint32_t count = mListeners.Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    Listener* listener = &mListeners.ElementAt(i);
+    if (!listener->mFlags.mInSystemGroup &&
+        listener->mFlags.mAllowUntrustedEvents &&
+        (listener->mTypeAtom == nsGkAtoms::onkeydown ||
+         listener->mTypeAtom == nsGkAtoms::onkeypress ||
+         listener->mTypeAtom == nsGkAtoms::onkeyup)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
 EventListenerManager::HasApzAwareListeners()
 {
   uint32_t count = mListeners.Length();
   for (uint32_t i = 0; i < count; ++i) {
     Listener* listener = &mListeners.ElementAt(i);
     if (IsApzAwareListener(listener)) {
       return true;
     }
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -463,16 +463,18 @@ public:
   }
 
   void MarkForCC();
 
   void TraceListeners(JSTracer* aTrc);
 
   dom::EventTarget* GetTarget() { return mTarget; }
 
+  bool HasUntrustedOrNonSystemGroupKeyEventListeners();
+
   bool HasApzAwareListeners();
   bool IsApzAwareListener(Listener* aListener);
   bool IsApzAwareEvent(nsIAtom* aEvent);
 
 protected:
   void HandleEventInternal(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIDOMEvent** aDOMEvent,
--- a/dom/events/EventTarget.cpp
+++ b/dom/events/EventTarget.cpp
@@ -53,16 +53,23 @@ EventTarget::SetEventHandler(const nsASt
 void
 EventTarget::SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                              EventHandlerNonNull* aHandler)
 {
   GetOrCreateListenerManager()->SetEventHandler(aType, aTypeString, aHandler);
 }
 
 bool
+EventTarget::HasUntrustedOrNonSystemGroupKeyEventListeners() const
+{
+  EventListenerManager* elm = GetExistingListenerManager();
+  return elm && elm->HasUntrustedOrNonSystemGroupKeyEventListeners();
+}
+
+bool
 EventTarget::IsApzAware() const
 {
   EventListenerManager* elm = GetExistingListenerManager();
   return elm && elm->HasApzAwareListeners();
 }
 
 bool
 EventTarget::DispatchEvent(Event& aEvent,
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -94,16 +94,20 @@ public:
    * Get the event listener manager, returning null if it does not already
    * exist.
    */
   virtual EventListenerManager* GetExistingListenerManager() const = 0;
 
   // Called from AsyncEventDispatcher to notify it is running.
   virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) {}
 
+  // Used by FocusTarget to determine whether this event target has listeners
+  // for untrusted or non system group key events.
+  bool HasUntrustedOrNonSystemGroupKeyEventListeners() const;
+
   virtual bool IsApzAware() const;
 
 protected:
   EventHandlerNonNull* GetEventHandler(nsIAtom* aType,
                                        const nsAString& aTypeString);
   void SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                        EventHandlerNonNull* aHandler);
 };
--- a/gfx/layers/apz/src/FocusState.cpp
+++ b/gfx/layers/apz/src/FocusState.cpp
@@ -4,46 +4,51 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/FocusState.h"
 
 namespace mozilla {
 namespace layers {
 
 FocusState::FocusState()
-  : mFocusLayersId(0)
+  : mFocusHasKeyEventListeners(false)
+  , mFocusLayersId(0)
   , mFocusHorizontalTarget(FrameMetrics::NULL_SCROLL_ID)
   , mFocusVerticalTarget(FrameMetrics::NULL_SCROLL_ID)
 {
 }
 
 void
 FocusState::Update(uint64_t aRootLayerTreeId,
                    uint64_t aOriginatingLayersId,
                    const FocusTarget& aState)
 {
   // Update the focus tree with the latest target
   mFocusTree[aOriginatingLayersId] = aState;
 
   // Reset our internal state so we can recalculate it
+  mFocusHasKeyEventListeners = false;
   mFocusLayersId = aRootLayerTreeId;
   mFocusHorizontalTarget = FrameMetrics::NULL_SCROLL_ID;
   mFocusVerticalTarget = FrameMetrics::NULL_SCROLL_ID;
 
   // To update the focus state for the entire APZCTreeManager, we need
   // to traverse the focus tree to find the current leaf which is the global
   // focus target we can use for async keyboard scrolling
   while (true) {
     auto currentNode = mFocusTree.find(mFocusLayersId);
     if (currentNode == mFocusTree.end()) {
       return;
     }
 
     const FocusTarget& target = currentNode->second;
 
+    // Accumulate event listener flags on the path to the focus target
+    mFocusHasKeyEventListeners |= target.mFocusHasKeyEventListeners;
+
     switch (target.mType) {
       case FocusTarget::eRefLayer: {
         // Guard against infinite loops
         MOZ_ASSERT(mFocusLayersId != target.mData.mRefLayerId);
         if (mFocusLayersId == target.mData.mRefLayerId) {
           return;
         }
 
--- a/gfx/layers/apz/src/FocusState.h
+++ b/gfx/layers/apz/src/FocusState.h
@@ -91,16 +91,20 @@ public:
    * Removes a focus target by its layer tree ID.
    */
   void RemoveFocusTarget(uint64_t aLayersId);
 
 private:
   // The set of focus targets received indexed by their layer tree ID
   std::unordered_map<uint64_t, FocusTarget> mFocusTree;
 
+  // A flag whether there is a key listener on the event target chain for the
+  // focused element
+  bool mFocusHasKeyEventListeners;
+
   // The layer tree ID which contains the scrollable frame of the focused element
   uint64_t mFocusLayersId;
   // The scrollable layer corresponding to the scrollable frame that is used to
   // scroll the focused element. This depends on the direction the user is
   // scrolling.
   FrameMetrics::ViewID mFocusHorizontalTarget;
   FrameMetrics::ViewID mFocusVerticalTarget;
 };
--- a/gfx/layers/apz/src/FocusTarget.cpp
+++ b/gfx/layers/apz/src/FocusTarget.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/TabParent.h"  // for TabParent
+#include "mozilla/dom/EventTarget.h" // for EventTarget
+#include "mozilla/dom/TabParent.h"   // for TabParent
+#include "mozilla/EventDispatcher.h" // for EventDispatcher
 #include "mozilla/layout/RenderFrameParent.h" // For RenderFrameParent
 #include "nsIPresShell.h"  // for nsIPresShell
 #include "nsLayoutUtils.h" // for nsLayoutUtils
 
 using namespace mozilla::dom;
 using namespace mozilla::layout;
 
 namespace mozilla {
@@ -33,32 +35,59 @@ GetRetargetEventPresShell(nsIPresShell* 
   if (!retargetEventDoc) {
     return nullptr;
   }
 
   nsCOMPtr<nsIPresShell> presShell = retargetEventDoc->GetShell();
   return presShell.forget();
 }
 
-FocusTarget::FocusTarget() : mType(FocusTarget::eNone)
+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]->HasUntrustedOrNonSystemGroupKeyEventListeners()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+FocusTarget::FocusTarget()
+  : mFocusHasKeyEventListeners(false)
+  , mType(FocusTarget::eNone)
 {
 }
 
 FocusTarget::FocusTarget(nsIPresShell* aRootPresShell)
+  : mFocusHasKeyEventListeners(false)
 {
   MOZ_ASSERT(aRootPresShell);
   MOZ_ASSERT(NS_IsMainThread());
 
   // Key events can be retargeted to a child PresShell when there is an iframe
   nsCOMPtr<nsIPresShell> presShell = GetRetargetEventPresShell(aRootPresShell);
 
   // Get the content that should be scrolled for this PresShell, which is
   // the current focused element or the current DOM selection
   nsCOMPtr<nsIContent> scrollTarget = presShell->GetContentForScrolling();
 
+  // Collect event listener information so we can track what is potentially focus
+  // changing
+  mFocusHasKeyEventListeners = HasListenersForKeyEvents(scrollTarget);
+
   // Check if the scroll target is a remote browser
   if (TabParent* browserParent = TabParent::GetFrom(scrollTarget)) {
     RenderFrameParent* rfp = browserParent->GetRenderFrame();
 
     // The globally focused element for scrolling is in a remote layer tree
     if (rfp) {
       mType = FocusTarget::eRefLayer;
       mData.mRefLayerId = rfp->GetLayersId();
--- a/gfx/layers/apz/src/FocusTarget.h
+++ b/gfx/layers/apz/src/FocusTarget.h
@@ -48,16 +48,20 @@ public:
   FocusTarget();
 
   /**
    * Construct a focus target for the specified top level PresShell
    */
   FocusTarget(nsIPresShell* aRootPresShell);
 
 public:
+  // Whether there are keydown, keypress, or keyup event listeners
+  // in the event target chain of the focused element
+  bool mFocusHasKeyEventListeners;
+
   FocusTargetType mType;
   FocusTargetData mData;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_FocusTarget_h