Bug 1399956 - Track top level windows in headless mode for window ordering. r?jrmuizel draft
authorBrendan Dahl <brendan.dahl@gmail.com>
Thu, 17 Aug 2017 16:53:21 -0700
changeset 675231 2d5747516d6988595d0de909b6743023f587f59b
parent 670812 7d15bc419c6cd7e9f3b4d41370c3b0e5990c8d1b
child 675232 ccc59a84d68f108d694aa2e008d86dbbc273a41a
child 677764 f3dcd1ceb17139d9f19a8f49968eee21927fb04b
push id83075
push userbmo:bdahl@mozilla.com
push dateThu, 05 Oct 2017 01:54:43 +0000
reviewersjrmuizel
bugs1399956
milestone58.0a1
Bug 1399956 - Track top level windows in headless mode for window ordering. r?jrmuizel This better emulates a window manager and triggers the required activated/deactivated events on the proper windows as they are closed or hidden. MozReview-Commit-ID: 1A2JTp8i4VE
widget/headless/HeadlessWidget.cpp
widget/headless/HeadlessWidget.h
--- a/widget/headless/HeadlessWidget.cpp
+++ b/widget/headless/HeadlessWidget.cpp
@@ -3,16 +3,17 @@
  * 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 "HeadlessWidget.h"
 #include "HeadlessCompositorWidget.h"
 #include "Layers.h"
 #include "BasicLayers.h"
 #include "BasicEvents.h"
 #include "mozilla/gfx/gfxVars.h"
+#include "mozilla/ClearOnShutdown.h"
 
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 /*static*/ already_AddRefed<nsIWidget>
 nsIWidget::CreateHeadlessWidget()
 {
   nsCOMPtr<nsIWidget> widget = new mozilla::widget::HeadlessWidget();
@@ -30,33 +31,79 @@ CreateDefaultTarget(IntSize aSize)
   IntSize size = (aSize.width <= 0 || aSize.height <= 0) ? gfx::IntSize(1, 1) : aSize;
   RefPtr<DrawTarget> target = Factory::CreateDrawTarget(gfxVars::ContentBackend(), size, SurfaceFormat::B8G8R8A8);
   RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(target);
   return ctx.forget();
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(HeadlessWidget, nsBaseWidget)
 
-HeadlessWidget* HeadlessWidget::sActiveWindow = nullptr;
+StaticAutoPtr<nsTArray<HeadlessWidget*>> HeadlessWidget::sActiveWindows;
+
+already_AddRefed<HeadlessWidget>
+HeadlessWidget::GetActiveWindow()
+{
+  if (!sActiveWindows) {
+    return nullptr;
+  }
+  auto length = sActiveWindows->Length();
+  if (length == 0) {
+    return nullptr;
+  }
+  RefPtr<HeadlessWidget> widget = sActiveWindows->ElementAt(length - 1);
+  return widget.forget();
+}
 
 HeadlessWidget::HeadlessWidget()
   : mEnabled(true)
   , mVisible(false)
+  , mDestroyed(false)
   , mTopLevel(nullptr)
   , mCompositorWidget(nullptr)
   , mLastSizeMode(nsSizeMode_Normal)
   , mEffectiveSizeMode(nsSizeMode_Normal)
   , mRestoreBounds(0,0,0,0)
 {
+  if (!sActiveWindows) {
+    sActiveWindows = new nsTArray<HeadlessWidget*>();
+    ClearOnShutdown(&sActiveWindows);
+  }
 }
 
 HeadlessWidget::~HeadlessWidget()
 {
-  if (sActiveWindow == this)
-    sActiveWindow = nullptr;
+  Destroy();
+}
+
+void
+HeadlessWidget::Destroy()
+{
+  if (mDestroyed) {
+    return;
+  }
+  mDestroyed = true;
+
+  if (sActiveWindows) {
+    int32_t index = sActiveWindows->IndexOf(this);
+    if (index != -1) {
+      RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
+      sActiveWindows->RemoveElementAt(index);
+      // If this is the currently active widget and there's a previously active
+      // widget, activate the previous widget.
+      RefPtr<HeadlessWidget> previousActiveWindow = GetActiveWindow();
+      if (this == activeWindow && previousActiveWindow &&
+          previousActiveWindow->mWidgetListener) {
+        previousActiveWindow->mWidgetListener->WindowActivated();
+      }
+    }
+  }
+
+  nsBaseWidget::OnDestroy();
+
+  nsBaseWidget::Destroy();
 }
 
 nsresult
 HeadlessWidget::Create(nsIWidget* aParent,
                        nsNativeWidget aNativeParent,
                        const LayoutDeviceIntRect& aRect,
                        nsWidgetInitData* aInitData)
 {
@@ -100,45 +147,56 @@ nsIWidget*
 HeadlessWidget::GetTopLevelWidget()
 {
   return mTopLevel;
 }
 
 void
 HeadlessWidget::RaiseWindow()
 {
-  MOZ_ASSERT(mTopLevel == this, "Raising a non-toplevel window.");
+  MOZ_ASSERT(mTopLevel == this || mWindowType == eWindowType_dialog, "Raising a non-toplevel window.");
 
-  if (sActiveWindow == this)
+  // Do nothing if this is the currently active window.
+  RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
+  if (activeWindow == this) {
     return;
+  }
 
   // Raise the window to the top of the stack.
   nsWindowZ placement = nsWindowZTop;
   nsCOMPtr<nsIWidget> actualBelow;
   if (mWidgetListener)
     mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
 
   // Deactivate the last active window.
-  if (sActiveWindow && sActiveWindow->mWidgetListener)
-    sActiveWindow->mWidgetListener->WindowDeactivated();
+  if (activeWindow && activeWindow->mWidgetListener) {
+    activeWindow->mWidgetListener->WindowDeactivated();
+  }
+
+  // Remove this window if it's already tracked.
+  int32_t index = sActiveWindows->IndexOf(this);
+  if (index != -1) {
+    sActiveWindows->RemoveElementAt(index);
+  }
 
   // Activate this window.
-  sActiveWindow = this;
+  sActiveWindows->AppendElement(this);
   if (mWidgetListener)
     mWidgetListener->WindowActivated();
 }
 
 void
 HeadlessWidget::Show(bool aState)
 {
   mVisible = aState;
 
-  // Top-level windows are activated/raised when shown.
-  if (aState && mTopLevel == this)
+  // Top-level window and dialogs are activated/raised when shown.
+  if (aState && (mTopLevel == this || mWindowType == eWindowType_dialog)) {
     RaiseWindow();
+  }
 
   ApplySizeModeSideEffects();
 }
 
 bool
 HeadlessWidget::IsVisible() const
 {
   return mVisible;
--- a/widget/headless/HeadlessWidget.h
+++ b/widget/headless/HeadlessWidget.h
@@ -35,16 +35,17 @@ public:
   virtual already_AddRefed<nsIWidget> CreateChild(const LayoutDeviceIntRect& aRect,
                                                   nsWidgetInitData* aInitData = nullptr,
                                                   bool aForceUseIWidgetParent = false) override;
 
   virtual nsIWidget* GetTopLevelWidget() override;
 
   virtual void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override;
 
+  virtual void Destroy() override;
   virtual void Show(bool aState) override;
   virtual bool IsVisible() const override;
   virtual void Move(double aX, double aY) override;
   virtual void Resize(double aWidth,
                       double aHeight,
                       bool   aRepaint) override;
   virtual void Resize(double aX,
                       double aY,
@@ -90,29 +91,35 @@ public:
 
   virtual nsresult DispatchEvent(WidgetGUIEvent* aEvent,
                                  nsEventStatus& aStatus) override;
 
 private:
   ~HeadlessWidget();
   bool mEnabled;
   bool mVisible;
+  bool mDestroyed;
   nsIWidget* mTopLevel;
   HeadlessCompositorWidget* mCompositorWidget;
   // The size mode before entering fullscreen mode.
   nsSizeMode mLastSizeMode;
   // The last size mode set while the window was visible.
   nsSizeMode mEffectiveSizeMode;
   InputContext mInputContext;
   // In headless there is no window manager to track window bounds
   // across size mode changes, so we must track it to emulate.
   LayoutDeviceIntRect mRestoreBounds;
   void ApplySizeModeSideEffects();
   // Similarly, we must track the active window ourselves in order
   // to dispatch (de)activation events properly.
   void RaiseWindow();
-  static HeadlessWidget* sActiveWindow;
+  // The top level widgets are tracked for window ordering. They are
+  // stored in order of activation where the last element is always the
+  // currently active widget.
+  static StaticAutoPtr<nsTArray<HeadlessWidget*>> sActiveWindows;
+  // Get the most recently activated widget or null if there are none.
+  static already_AddRefed<HeadlessWidget>GetActiveWindow();
 };
 
 } // namespace widget
 } // namespace mozilla
 
 #endif