Bug 1535537 - Maintain information about focused TabParent on chrome main thread independently of APZ. r=nika
authorHenri Sivonen <hsivonen@hsivonen.fi>
Tue, 19 Mar 2019 13:37:14 +0000
changeset 465036 dbe5292cac3341db4dc13af40082afd722b06830
parent 465035 89c31db7b51ec20a9182531f16ef7d65dd97a937
child 465037 aa59ec8e0542373a561b259801dd84d2363b3daa
push id80847
push userhsivonen@mozilla.com
push dateTue, 19 Mar 2019 13:39:36 +0000
treeherderautoland@dbe5292cac33 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1535537
milestone68.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 1535537 - Maintain information about focused TabParent on chrome main thread independently of APZ. r=nika Differential Revision: https://phabricator.services.mozilla.com/D23632
dom/events/IMEStateManager.cpp
dom/events/IMEStateManager.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
layout/build/nsLayoutStatics.cpp
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -196,16 +196,22 @@ void IMEStateManager::OnTabParentDestroy
 
   // The active remote process might have crashed.
   sActiveTabParent = nullptr;
 
   // XXX: Need to disable IME?
 }
 
 // static
+void IMEStateManager::OnFocusMovedBetweenBrowsers(TabParent* aBlur,
+                                                  TabParent* aFocus) {
+  MOZ_ASSERT(aBlur != aFocus);
+}
+
+// static
 void IMEStateManager::WidgetDestroyed(nsIWidget* aWidget) {
   if (sWidget == aWidget) {
     sWidget = nullptr;
   }
   if (sFocusedIMEWidget == aWidget) {
     NotifyIMEOfBlurForChildProcess();
     sFocusedIMEWidget = nullptr;
   }
--- a/dom/events/IMEStateManager.h
+++ b/dom/events/IMEStateManager.h
@@ -73,16 +73,22 @@ class IMEStateManager {
   }
 
   /**
    * OnTabParentDestroying() is called when aTabParent is being destroyed.
    */
   static void OnTabParentDestroying(TabParent* aTabParent);
 
   /**
+   * Focus moved between browsers from aBlur to aFocus. (nullptr means the
+   * chrome process.)
+   */
+  static void OnFocusMovedBetweenBrowsers(TabParent* aBlur, TabParent* aFocus);
+
+  /**
    * Called when aWidget is being deleted.
    */
   static void WidgetDestroyed(nsIWidget* aWidget);
 
   /**
    * GetWidgetForActiveInputContext() returns a widget which IMEStateManager
    * is managing input context with.  If a widget instance needs to cache
    * the last input context for nsIWidget::GetInputContext() or something,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -131,16 +131,19 @@ using namespace mozilla::gfx;
 
 using mozilla::Unused;
 
 LazyLogModule gBrowserFocusLog("BrowserFocus");
 
 #define LOGBROWSERFOCUS(args) \
   MOZ_LOG(gBrowserFocusLog, mozilla::LogLevel::Debug, args)
 
+/* static */
+StaticAutoPtr<nsTArray<TabParent*>> TabParent::sFocusStack;
+
 // The flags passed by the webProgress notifications are 16 bits shifted
 // from the ones registered by webProgressListeners.
 #define NOTIFY_FLAG_SHIFT 16
 
 namespace mozilla {
 namespace dom {
 
 TabParent::LayerToTabParentTable* TabParent::sLayerToTabParentTable = nullptr;
@@ -347,16 +350,17 @@ void TabParent::RemoveWindowListeners() 
     if (eventTarget) {
       eventTarget->RemoveEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
                                        this, false);
     }
   }
 }
 
 void TabParent::DestroyInternal() {
+  PopFocus(this);
   IMEStateManager::OnTabParentDestroying(this);
 
   RemoveWindowListeners();
 
 #ifdef ACCESSIBILITY
   if (a11y::DocAccessibleParent* tabDoc = GetTopLevelDocAccessible()) {
     tabDoc->Destroy();
   }
@@ -833,22 +837,24 @@ void TabParent::HandleAccessKey(const Wi
     WidgetKeyboardEvent localEvent(aEvent);
     Unused << SendHandleAccessKey(localEvent, aCharCodes);
   }
 }
 
 void TabParent::Activate() {
   LOGBROWSERFOCUS(("Activate %p", this));
   if (!mIsDestroyed) {
+    PushFocus(this);  // Intentionally inside "if"
     Unused << Manager()->SendActivate(this);
   }
 }
 
 void TabParent::Deactivate() {
   LOGBROWSERFOCUS(("Deactivate %p", this));
+  PopFocus(this);  // Intentionally outside "if"
   if (!mIsDestroyed) {
     Unused << Manager()->SendDeactivate(this);
   }
 }
 
 a11y::PDocAccessibleParent* TabParent::AllocPDocAccessibleParent(
     PDocAccessibleParent* aParent, const uint64_t&, const uint32_t&,
     const IAccessibleHolder&) {
@@ -2300,16 +2306,115 @@ bool TabParent::SendSelectionEvent(Widge
 bool TabParent::SendPasteTransferable(
     const IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData,
     const IPC::Principal& aRequestingPrincipal,
     const uint32_t& aContentPolicyType) {
   return PBrowserParent::SendPasteTransferable(
       aDataTransfer, aIsPrivateData, aRequestingPrincipal, aContentPolicyType);
 }
 
+/* static */
+void TabParent::InitializeStatics() {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  sFocusStack = new nsTArray<TabParent*>();
+  ClearOnShutdown(&sFocusStack);
+}
+
+/* static */
+TabParent* TabParent::GetFocused() {
+  if (!sFocusStack) {
+    return nullptr;
+  }
+  if (sFocusStack->IsEmpty()) {
+    return nullptr;
+  }
+  return sFocusStack->LastElement();
+}
+
+/* static */
+void TabParent::PushFocus(TabParent* aTabParent) {
+  if (!sFocusStack) {
+    MOZ_ASSERT_UNREACHABLE("PushFocus when not initialized");
+    return;
+  }
+  if (!aTabParent->GetBrowserBridgeParent()) {
+    // top-level Web content
+    if (!sFocusStack->IsEmpty()) {
+      // When a new native window is created, we spin a nested event loop.
+      // As a result, unlike when raising an existing window, we get
+      // PushFocus for content in the new window before we get the PopFocus
+      // for content in the old one. Hence, if the stack isn't empty when
+      // pushing top-level Web content, first pop everything off the stack.
+      LOGBROWSERFOCUS(
+          ("PushFocus for top-level Web content needs to clear the stack %p",
+           aTabParent));
+      PopFocus(sFocusStack->ElementAt(0));
+    }
+    MOZ_ASSERT(sFocusStack->IsEmpty());
+  } else {
+    // out-of-process iframe
+    // Considering that we can get top-level pushes out of order, let's
+    // ignore trailing out-of-process iframe pushes for the previous top-level
+    // Web content.
+    if (sFocusStack->IsEmpty()) {
+      LOGBROWSERFOCUS(
+          ("PushFocus for out-of-process iframe ignored with empty stack %p",
+           aTabParent));
+      return;
+    }
+    nsCOMPtr<nsIWidget> webRootWidget = sFocusStack->ElementAt(0)->GetWidget();
+    nsCOMPtr<nsIWidget> iframeWigdet = aTabParent->GetWidget();
+    if (webRootWidget != iframeWigdet) {
+      LOGBROWSERFOCUS(
+          ("PushFocus for out-of-process iframe ignored with mismatching "
+           "top-level content %p",
+           aTabParent));
+      return;
+    }
+  }
+  if (sFocusStack->Contains(aTabParent)) {
+    MOZ_ASSERT_UNREACHABLE(
+        "Trying to push a TabParent that is already on the stack");
+    return;
+  }
+  TabParent* old = GetFocused();
+  sFocusStack->AppendElement(aTabParent);
+  MOZ_ASSERT(GetFocused() == aTabParent);
+  LOGBROWSERFOCUS(("PushFocus changed focus to %p", aTabParent));
+  IMEStateManager::OnFocusMovedBetweenBrowsers(old, aTabParent);
+}
+
+/* static */
+void TabParent::PopFocus(TabParent* aTabParent) {
+  if (!sFocusStack) {
+    MOZ_ASSERT_UNREACHABLE("PopFocus when not initialized");
+    return;
+  }
+  // When focus is in an out-of-process iframe and the whole window
+  // or tab loses focus, we first receive a pop for the top-level Web
+  // content process and only then for its out-of-process iframes.
+  // Hence, we do all the popping up front and then ignore the
+  // pop requests for the out-of-process iframes that we already
+  // popped.
+  auto pos = sFocusStack->LastIndexOf(aTabParent);
+  if (pos == nsTArray<TabParent*>::NoIndex) {
+    LOGBROWSERFOCUS(("PopFocus not on stack %p", aTabParent));
+    return;
+  }
+  auto len = sFocusStack->Length();
+  auto itemsToPop = len - pos;
+  LOGBROWSERFOCUS(("PopFocus pops %zu items %p", itemsToPop, aTabParent));
+  while (pos < sFocusStack->Length()) {
+    TabParent* popped = sFocusStack->PopLastElement();
+    TabParent* focused = GetFocused();
+    LOGBROWSERFOCUS(("PopFocus changed focus to %p", focused));
+    IMEStateManager::OnFocusMovedBetweenBrowsers(popped, focused);
+  }
+}
+
 /*static*/
 TabParent* TabParent::GetFrom(nsFrameLoader* aFrameLoader) {
   if (!aFrameLoader) {
     return nullptr;
   }
   PBrowserParent* remoteBrowser = aFrameLoader->GetRemoteBrowser();
   return static_cast<TabParent*>(remoteBrowser);
 }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -467,16 +467,25 @@ class TabParent final : public PBrowserP
 
   bool HandleQueryContentEvent(mozilla::WidgetQueryContentEvent& aEvent);
 
   bool SendPasteTransferable(const IPCDataTransfer& aDataTransfer,
                              const bool& aIsPrivateData,
                              const IPC::Principal& aRequestingPrincipal,
                              const uint32_t& aContentPolicyType);
 
+  // Call from LayoutStatics only
+  static void InitializeStatics();
+
+  /**
+   * Returns the focused TabParent or nullptr if chrome or another app
+   * is focused.
+   */
+  static TabParent* GetFocused();
+
   static TabParent* GetFrom(nsFrameLoader* aFrameLoader);
 
   static TabParent* GetFrom(nsITabParent* aTabParent);
 
   static TabParent* GetFrom(PBrowserParent* aTabParent);
 
   static TabParent* GetFrom(nsIContent* aContent);
 
@@ -775,16 +784,23 @@ class TabParent final : public PBrowserP
   typedef nsDataHashtable<nsUint64HashKey, TabParent*> LayerToTabParentTable;
   static LayerToTabParentTable* sLayerToTabParentTable;
 
   static void AddTabParentToTable(layers::LayersId aLayersId,
                                   TabParent* aTabParent);
 
   static void RemoveTabParentFromTable(layers::LayersId aLayersId);
 
+  // Keeps track of which TabParent has keyboard focus
+  static StaticAutoPtr<nsTArray<TabParent*>> sFocusStack;
+
+  static void PushFocus(TabParent* aTabParent);
+
+  static void PopFocus(TabParent* aTabParent);
+
   layout::RenderFrame mRenderFrame;
   LayersObserverEpoch mLayerTreeEpoch;
 
   Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix;
 
   // If this flag is set, then the tab's layers will be preserved even when
   // the tab's docshell is inactive.
   bool mPreserveLayers;
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -113,16 +113,17 @@
 #include "mozilla/dom/U2FTokenManager.h"
 #ifdef OS_WIN
 #  include "mozilla/dom/WinWebAuthnManager.h"
 #endif
 #include "mozilla/dom/PointerEventHandler.h"
 #include "mozilla/dom/RemoteWorkerService.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/ReportingHeader.h"
+#include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
 #include "mozilla/dom/localstorage/ActorsParent.h"
 #include "mozilla/net/UrlClassifierFeatureFactory.h"
 #include "nsThreadManager.h"
 #include "mozilla/css/ImageLoader.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
@@ -289,16 +290,18 @@ nsresult nsLayoutStatics::Initialize() {
   mozilla::dom::WinWebAuthnManager::Initialize();
 #endif
 
   if (XRE_IsParentProcess()) {
     // On content process we initialize these components when PContentChild is
     // fully initialized.
     mozilla::dom::DOMPrefs::Initialize();
     mozilla::dom::RemoteWorkerService::Initialize();
+    // This one should be initialized on the parent only
+    mozilla::dom::TabParent::InitializeStatics();
   }
 
   nsThreadManager::InitializeShutdownObserver();
 
   mozilla::Fuzzyfox::Start();
 
   ClearSiteData::Initialize();