Bug 1466208 - part 29: Create PresShell::EventHandler::ComputeFocusedEventTargetElement() r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 04 Mar 2019 06:11:41 +0000
changeset 520101 664b3bc4a44924722ff985840ac1041bd45f3e74
parent 520100 ba13685290eef66ac658c4a0df6f40c81c801cf3
child 520102 4565c0c6aea8295b8f5df972fb12098752d43e19
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1466208
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1466208 - part 29: Create PresShell::EventHandler::ComputeFocusedEventTargetElement() r=smaug Most remaining code in `PresShell::EventHandler::HandleEvent()` is what computes event target of the event which should be handled on focused content. This patch moves the part to the new method. Additionally, moves `nsIPresShell::gKeyDownTarget` to `EventHandler::sLastKeyDownEventTargetElement` and make it use `StaticRefPtr`. Finally, for using `Element*` instead of `nsIContent*`, changes the result type of `Document::GetUnfocusedKeyEventTarget()` to `Element*`. Differential Revision: https://phabricator.services.mozilla.com/D21195
dom/base/Document.cpp
dom/base/Document.h
dom/html/nsHTMLDocument.cpp
dom/html/nsHTMLDocument.h
layout/base/PresShell.cpp
layout/base/PresShell.h
layout/base/nsIPresShell.h
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3866,17 +3866,17 @@ Element* Document::FindContentForSubDocu
 bool Document::IsNodeOfType(uint32_t aFlags) const { return false; }
 
 Element* Document::GetRootElement() const {
   return (mCachedRootElement && mCachedRootElement->GetParentNode() == this)
              ? mCachedRootElement
              : GetRootElementInternal();
 }
 
-nsIContent* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
+Element* Document::GetUnfocusedKeyEventTarget() { return GetRootElement(); }
 
 Element* Document::GetRootElementInternal() const {
   // We invoke GetRootElement() immediately before the servo traversal, so we
   // should always have a cache hit from Servo.
   MOZ_ASSERT(NS_IsMainThread());
 
   // Loop backwards because any non-elements, such as doctypes and PIs
   // are likely to appear before the root element.
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -1337,17 +1337,17 @@ class Document : public nsINode,
       mozilla::ErrorResult& aRv);
   already_AddRefed<mozilla::dom::Promise> RequestStorageAccess(
       mozilla::ErrorResult& aRv);
 
   /**
    * Gets the event target to dispatch key events to if there is no focused
    * content in the document.
    */
-  virtual nsIContent* GetUnfocusedKeyEventTarget();
+  virtual Element* GetUnfocusedKeyEventTarget();
 
   /**
    * Retrieve information about the viewport as a data structure.
    * This will return information in the viewport META data section
    * of the document. This can be used in lieu of ProcessViewportInfo(),
    * which places the viewport information in the document header instead
    * of returning it directly.
    *
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -750,17 +750,17 @@ void nsHTMLDocument::SetCompatibilityMod
   }
   mCompatMode = aMode;
   CSSLoader()->SetCompatibilityMode(mCompatMode);
   if (nsPresContext* pc = GetPresContext()) {
     pc->CompatibilityModeChanged();
   }
 }
 
-nsIContent* nsHTMLDocument::GetUnfocusedKeyEventTarget() {
+Element* nsHTMLDocument::GetUnfocusedKeyEventTarget() {
   if (nsGenericHTMLElement* body = GetBody()) {
     return body;
   }
   return Document::GetUnfocusedKeyEventTarget();
 }
 
 already_AddRefed<nsIURI> nsHTMLDocument::GetDomainURI() {
   nsIPrincipal* principal = NodePrincipal();
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -68,17 +68,17 @@ class nsHTMLDocument : public mozilla::d
   virtual void BeginLoad() override;
   virtual void EndLoad() override;
 
   // nsIHTMLDocument
   virtual void SetCompatibilityMode(nsCompatibility aMode) override;
 
   virtual bool IsWriting() override { return mWriteLevel != uint32_t(0); }
 
-  virtual nsIContent* GetUnfocusedKeyEventTarget() override;
+  virtual Element* GetUnfocusedKeyEventTarget() override;
 
   nsContentList* GetExistingForms() const { return mForms; }
 
   mozilla::dom::HTMLAllCollection* All();
 
   // Returns whether an object was found for aName.
   bool ResolveName(JSContext* aCx, const nsAString& aName,
                    JS::MutableHandle<JS::Value> aRetval,
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -209,17 +209,16 @@ using namespace mozilla::layers;
 using namespace mozilla::gfx;
 using namespace mozilla::layout;
 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
 typedef ScrollableLayerGuid::ViewID ViewID;
 
 CapturingContentInfo nsIPresShell::gCaptureInfo = {
     false /* mAllowed */, false /* mPointerLock */,
     false /* mRetargetToElement */, false /* mPreventDrag */};
-nsIContent* nsIPresShell::gKeyDownTarget;
 
 // RangePaintInfo is used to paint ranges to offscreen buffers
 struct RangePaintInfo {
   RefPtr<nsRange> mRange;
   nsDisplayListBuilder mBuilder;
   nsDisplayList mList;
 
   // offset of builder's reference frame to the root frame
@@ -628,16 +627,17 @@ bool nsIPresShell::DirtyRootsList::Frame
 }
 
 bool PresShell::sDisableNonTestMouseEvents = false;
 
 mozilla::LazyLogModule nsIPresShell::gLog("PresShell");
 
 mozilla::TimeStamp PresShell::EventHandler::sLastInputCreated;
 mozilla::TimeStamp PresShell::EventHandler::sLastInputProcessed;
+StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement;
 
 bool PresShell::sProcessInteractable = false;
 
 static bool gVerifyReflowEnabled;
 
 bool nsIPresShell::GetVerifyReflowEnable() {
 #ifdef DEBUG
   static bool firstTime = true;
@@ -1198,19 +1198,17 @@ void PresShell::Destroy() {
 
     mDocAccessible->Shutdown();
     mDocAccessible = nullptr;
   }
 #endif  // ACCESSIBILITY
 
   MaybeReleaseCapturingContent();
 
-  if (gKeyDownTarget && gKeyDownTarget->OwnerDoc() == mDocument) {
-    NS_RELEASE(gKeyDownTarget);
-  }
+  EventHandler::OnPresShellDestroy(mDocument);
 
   if (mContentToScrollTo) {
     mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
     mContentToScrollTo = nullptr;
   }
 
   if (mPresContext) {
     // We need to notify the destroying the nsPresContext to ESM for
@@ -6541,73 +6539,36 @@ nsresult PresShell::EventHandler::Handle
 
     return NS_OK;
   }
 
   nsresult rv = NS_OK;
 
   PushCurrentEventInfo(nullptr, nullptr);
 
-  // key and IME related events go to the focused frame in this DOM window.
   if (aGUIEvent->IsTargetedAtFocusedContent()) {
     mPresShell->mCurrentEventContent = nullptr;
 
-    nsCOMPtr<nsPIDOMWindowOuter> window = GetDocument()->GetWindow();
-    nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
-    nsCOMPtr<nsIContent> eventTarget = nsFocusManager::GetFocusedDescendant(
-        window, nsFocusManager::eOnlyCurrentWindow,
-        getter_AddRefs(focusedWindow));
-
-    // otherwise, if there is no focused content or the focused content has
-    // no frame, just use the root content. This ensures that key events
-    // still get sent to the window properly if nothing is focused or if a
-    // frame goes away while it is focused.
-    if (!eventTarget || !eventTarget->GetPrimaryFrame()) {
-      eventTarget = GetDocument()->GetUnfocusedKeyEventTarget();
-    }
-
-    if (aGUIEvent->mMessage == eKeyDown) {
-      NS_IF_RELEASE(nsIPresShell::gKeyDownTarget);
-      NS_IF_ADDREF(nsIPresShell::gKeyDownTarget = eventTarget);
-    } else if ((aGUIEvent->mMessage == eKeyPress ||
-                aGUIEvent->mMessage == eKeyUp) &&
-               nsIPresShell::gKeyDownTarget) {
-      // If a different element is now focused for the keypress/keyup event
-      // than what was focused during the keydown event, check if the new
-      // focused element is not in a chrome document any more, and if so,
-      // retarget the event back at the keydown target. This prevents a
-      // content area from grabbing the focus from chrome in-between key
-      // events.
-      if (eventTarget) {
-        bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
-            nsIPresShell::gKeyDownTarget->GetComposedDoc());
-        if (keyDownIsChrome !=
-                nsContentUtils::IsChromeDoc(eventTarget->GetComposedDoc()) ||
-            (keyDownIsChrome && TabParent::GetFrom(eventTarget))) {
-          eventTarget = nsIPresShell::gKeyDownTarget;
-        }
-      }
-
-      if (aGUIEvent->mMessage == eKeyUp) {
-        NS_RELEASE(nsIPresShell::gKeyDownTarget);
-      }
-    }
+    RefPtr<Element> eventTargetElement =
+        ComputeFocusedEventTargetElement(aGUIEvent);
 
     mPresShell->mCurrentEventFrame = nullptr;
-    Document* targetDoc = eventTarget ? eventTarget->OwnerDoc() : nullptr;
+    Document* targetDoc =
+        eventTargetElement ? eventTargetElement->OwnerDoc() : nullptr;
     if (targetDoc && targetDoc != GetDocument()) {
       PopCurrentEventInfo();
       nsCOMPtr<nsIPresShell> shell = targetDoc->GetShell();
       if (shell) {
         rv = static_cast<PresShell*>(shell.get())
-                 ->HandleRetargetedEvent(aGUIEvent, aEventStatus, eventTarget);
+                 ->HandleRetargetedEvent(aGUIEvent, aEventStatus,
+                                         eventTargetElement);
       }
       return rv;
     } else {
-      mPresShell->mCurrentEventContent = eventTarget;
+      mPresShell->mCurrentEventContent = eventTargetElement;
     }
 
     if (!mPresShell->GetCurrentEventContent() ||
         !mPresShell->GetCurrentEventFrame() ||
         InZombieDocument(mPresShell->mCurrentEventContent)) {
       rv = RetargetEventToParent(aGUIEvent, aEventStatus);
       PopCurrentEventInfo();
       return rv;
@@ -7497,16 +7458,70 @@ PresShell::EventHandler::HandleEventWith
 
   EventHandler eventHandlerForCapturingContent(
       std::move(presShellForCapturingContent));
   return eventHandlerForCapturingContent.HandleEventWithTarget(
       aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
       overrideClickTarget);
 }
 
+Element* PresShell::EventHandler::ComputeFocusedEventTargetElement(
+    WidgetGUIEvent* aGUIEvent) {
+  MOZ_ASSERT(aGUIEvent);
+  MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
+
+  // key and IME related events go to the focused frame in this DOM window.
+  nsPIDOMWindowOuter* window = GetDocument()->GetWindow();
+  nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+  Element* eventTargetElement = nsFocusManager::GetFocusedDescendant(
+      window, nsFocusManager::eOnlyCurrentWindow,
+      getter_AddRefs(focusedWindow));
+
+  // otherwise, if there is no focused content or the focused content has
+  // no frame, just use the root content. This ensures that key events
+  // still get sent to the window properly if nothing is focused or if a
+  // frame goes away while it is focused.
+  if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) {
+    eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget();
+  }
+
+  switch (aGUIEvent->mMessage) {
+    case eKeyDown:
+      sLastKeyDownEventTargetElement = eventTargetElement;
+      return eventTargetElement;
+    case eKeyPress:
+    case eKeyUp:
+      if (!sLastKeyDownEventTargetElement) {
+        return eventTargetElement;
+      }
+      // If a different element is now focused for the keypress/keyup event
+      // than what was focused during the keydown event, check if the new
+      // focused element is not in a chrome document any more, and if so,
+      // retarget the event back at the keydown target. This prevents a
+      // content area from grabbing the focus from chrome in-between key
+      // events.
+      if (eventTargetElement) {
+        bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
+            sLastKeyDownEventTargetElement->GetComposedDoc());
+        if (keyDownIsChrome != nsContentUtils::IsChromeDoc(
+                                   eventTargetElement->GetComposedDoc()) ||
+            (keyDownIsChrome && TabParent::GetFrom(eventTargetElement))) {
+          eventTargetElement = sLastKeyDownEventTargetElement;
+        }
+      }
+
+      if (aGUIEvent->mMessage == eKeyUp) {
+        sLastKeyDownEventTargetElement = nullptr;
+      }
+      MOZ_FALLTHROUGH;
+    default:
+      return eventTargetElement;
+  }
+}
+
 Document* PresShell::GetPrimaryContentDocument() {
   nsPresContext* context = GetPresContext();
   if (!context || !context->IsRoot()) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
   if (!shellAsTreeItem) {
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -11,16 +11,17 @@
 
 #include "MobileViewportManager.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/HTMLDocumentBinding.h"
 #include "mozilla/layers/FocusTarget.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ServoStyleSet.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "nsContentUtils.h"  // For AddScriptBlocker().
 #include "nsCRT.h"
 #include "nsIObserver.h"
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
@@ -571,16 +572,27 @@ class PresShell final : public nsIPresSh
     nsresult HandleEventWithTarget(WidgetEvent* aEvent,
                                    nsIFrame* aNewEventFrame,
                                    nsIContent* aNewEventContent,
                                    nsEventStatus* aEventStatus,
                                    bool aIsHandlingNativeEvent,
                                    nsIContent** aTargetContent,
                                    nsIContent* aOverrideClickTarget);
 
+    /**
+     * OnPresShellDestroy() is called when every PresShell instance is being
+     * destroyed.
+     */
+    static inline void OnPresShellDestroy(Document* aDocument) {
+      if (sLastKeyDownEventTargetElement &&
+          sLastKeyDownEventTargetElement->OwnerDoc() == aDocument) {
+        sLastKeyDownEventTargetElement = nullptr;
+      }
+    }
+
    private:
     static bool InZombieDocument(nsIContent* aContent);
     static nsIFrame* GetNearestFrameContainingPresShell(
         nsIPresShell* aPresShell);
     static already_AddRefed<nsIURI> GetDocumentURIToCompareWithBlacklist(
         PresShell& aPresShell);
 
     /**
@@ -961,16 +973,27 @@ class PresShell final : public nsIPresSh
      *                                  HandeEventWithTraget().
      */
     MOZ_CAN_RUN_SCRIPT
     nsresult HandleEventWithPointerCapturingContentWithoutItsFrame(
         nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
         nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus);
 
     /**
+     * ComputeFocusedEventTargetElement() returns event target element for
+     * aGUIEvent which should be handled with focused content.
+     * This may set/unset sLastKeyDownEventTarget if necessary.
+     *
+     * @param aGUIEvent                 The handling event.
+     * @return                          The element which should be the event
+     *                                  target of aGUIEvent.
+     */
+    Element* ComputeFocusedEventTargetElement(WidgetGUIEvent* aGUIEvent);
+
+    /**
      * XXX Needs better name.
      * HandleEventInternal() dispatches aEvent into the DOM tree and
      * notify EventStateManager of that.
      *
      * @param aEvent                    Event to be dispatched.
      * @param aEventStatus              [in/out] EventStatus of aEvent.
      * @param aIsHandlingNativeEvent    true if aGUIEvent represents a native
      *                                  event.
@@ -1065,16 +1088,17 @@ class PresShell final : public nsIPresSh
     }
     void PushDelayedEventIntoQueue(UniquePtr<DelayedEvent>&& aDelayedEvent) {
       mPresShell->mDelayedEvents.AppendElement(std::move(aDelayedEvent));
     }
 
     OwningNonNull<PresShell> mPresShell;
     static TimeStamp sLastInputCreated;
     static TimeStamp sLastInputProcessed;
+    static StaticRefPtr<Element> sLastKeyDownEventTargetElement;
   };
 
   /**
    * Helper method of EventHandler::HandleEvent().  This is called when the
    * event is dispatched without ref-point and dispatched by
    * EventHandler::HandleEvent().
    *
    * See EventHandler::HandleRetargetedEvent() for the detail of the arguments.
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -2046,18 +2046,16 @@ class nsIPresShell : public nsStubDocume
   bool mIsNeverPainting : 1;
 
   // Whether the most recent change to the pres shell resolution was
   // originated by the main thread.
   bool mResolutionUpdated : 1;
 
   uint32_t mPresShellId;
 
-  static nsIContent* gKeyDownTarget;
-
   // Cached font inflation values. This is done to prevent changing of font
   // inflation until a page is reloaded.
   uint32_t mFontSizeInflationEmPerLine;
   uint32_t mFontSizeInflationMinTwips;
   uint32_t mFontSizeInflationLineThreshold;
 
   // Whether we're currently under a FlushPendingNotifications.
   // This is used to handle flush reentry correctly.