Linux: Reorder browser list on workspace switch

Opening a link from a 3rd party app picks the most recently
used profile/browser to open the new tab in.  Whenever a user
changes workspaces, reorder the browser list such that the
browsers in the current workspace are moved to the top of the
list.  This way, when a user opens a link, it will open in a
browser on the current workspace (if there are any browsers).

BUG=619673

Review-Url: https://codereview.chromium.org/2108933003
Cr-Commit-Position: refs/heads/master@{#404544}
diff --git a/chrome/browser/ui/browser_list.cc b/chrome/browser/ui/browser_list.cc
index 4c39d62..a7b8870 100644
--- a/chrome/browser/ui/browser_list.cc
+++ b/chrome/browser/ui/browser_list.cc
@@ -186,6 +186,36 @@
 }
 
 // static
+void BrowserList::MoveBrowsersInWorkspaceToFront(
+    const std::string& new_workspace) {
+  DCHECK(!new_workspace.empty());
+
+  BrowserList* instance = GetInstance();
+
+  Browser* old_last_active = instance->GetLastActive();
+  BrowserVector& last_active_browsers = instance->last_active_browsers_;
+
+  // Perform a stable partition on the browsers in the list so that the browsers
+  // in the new workspace appear after the browsers in the other workspaces.
+  //
+  // For example, if we have a list of browser-workspace pairs
+  // [{b1, 0}, {b2, 1}, {b3, 0}, {b4, 1}]
+  // and we switch to workspace 1, we want the resulting browser list to look
+  // like [{b1, 0}, {b3, 0}, {b2, 1}, {b4, 1}].
+  std::stable_partition(
+      last_active_browsers.begin(), last_active_browsers.end(),
+      [&new_workspace](Browser* browser) {
+        return browser->window()->GetWorkspace() != new_workspace;
+      });
+
+  Browser* new_last_active = instance->GetLastActive();
+  if (old_last_active != new_last_active) {
+    FOR_EACH_OBSERVER(chrome::BrowserListObserver, observers_.Get(),
+                      OnBrowserSetLastActive(new_last_active));
+  }
+}
+
+// static
 void BrowserList::SetLastActive(Browser* browser) {
   content::RecordAction(UserMetricsAction("ActiveBrowserChanged"));
 
diff --git a/chrome/browser/ui/browser_list.h b/chrome/browser/ui/browser_list.h
index 33fdb59f4..4270d45 100644
--- a/chrome/browser/ui/browser_list.h
+++ b/chrome/browser/ui/browser_list.h
@@ -69,6 +69,10 @@
   static void AddObserver(chrome::BrowserListObserver* observer);
   static void RemoveObserver(chrome::BrowserListObserver* observer);
 
+  // Moves all the browsers that show on workspace |new_workspace| to the end of
+  // the browser list (i.e. the browsers that were "activated" most recently).
+  static void MoveBrowsersInWorkspaceToFront(const std::string& new_workspace);
+
   // Called by Browser objects when their window is activated (focused).  This
   // allows us to determine what the last active Browser was on each desktop.
   static void SetLastActive(Browser* browser);
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc
index 8355677..ecd4f8ec 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/libgtk2ui/gtk2_ui.h"
 #include "chrome/browser/ui/simple_message_box.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -18,6 +19,7 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/prefs/pref_service.h"
+#include "ui/aura/env.h"
 #include "ui/aura/window.h"
 #include "ui/base/ime/input_method_initializer.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -28,6 +30,7 @@
 #include "ui/native_theme/native_theme_dark_aura.h"
 #include "ui/views/linux_ui/linux_ui.h"
 #include "ui/views/widget/desktop_aura/desktop_screen.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
 #include "ui/views/widget/native_widget_aura.h"
 
 namespace {
@@ -68,7 +71,11 @@
 }
 
 ChromeBrowserMainExtraPartsViewsLinux::
-    ~ChromeBrowserMainExtraPartsViewsLinux() {}
+    ~ChromeBrowserMainExtraPartsViewsLinux() {
+  // X11DesktopHandler is destructed at this point, so we don't need to remove
+  // ourselves as an X11DesktopHandlerObserver
+  DCHECK(!aura::Env::GetInstanceDontCreate());
+}
 
 void ChromeBrowserMainExtraPartsViewsLinux::PreEarlyInitialization() {
   // TODO(erg): Refactor this into a dlopen call when we add a GTK3 port.
@@ -89,6 +96,8 @@
   views::LinuxUI::instance()->MaterialDesignControllerReady();
   views::LinuxUI::instance()->UpdateDeviceScaleFactor(
       display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor());
+
+  views::X11DesktopHandler::get()->AddObserver(this);
 }
 
 void ChromeBrowserMainExtraPartsViewsLinux::PreProfileInit() {
@@ -117,3 +126,8 @@
 
   exit(EXIT_FAILURE);
 }
+
+void ChromeBrowserMainExtraPartsViewsLinux::OnWorkspaceChanged(
+    const std::string& new_workspace) {
+  BrowserList::MoveBrowsersInWorkspaceToFront(new_workspace);
+}
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h
index 0b542ed..ed3a86bd 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h
@@ -11,9 +11,11 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/views/chrome_browser_main_extra_parts_views.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler_observer.h"
 
 class ChromeBrowserMainExtraPartsViewsLinux
-    : public ChromeBrowserMainExtraPartsViews {
+    : public ChromeBrowserMainExtraPartsViews,
+      public views::X11DesktopHandlerObserver {
  public:
   ChromeBrowserMainExtraPartsViewsLinux();
   ~ChromeBrowserMainExtraPartsViewsLinux() override;
@@ -24,6 +26,9 @@
   void PreCreateThreads() override;
   void PreProfileInit() override;
 
+  // Overridden from views::X11DesktopHandlerObserver.
+  void OnWorkspaceChanged(const std::string& new_workspace) override;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainExtraPartsViewsLinux);
 };
diff --git a/chrome/browser/ui/views/frame/browser_frame.cc b/chrome/browser/ui/views/frame/browser_frame.cc
index d1c767c..37bcfbb 100644
--- a/chrome/browser/ui/views/frame/browser_frame.cc
+++ b/chrome/browser/ui/views/frame/browser_frame.cc
@@ -41,6 +41,10 @@
 #include "chrome/browser/ui/views/frame/browser_command_handler_linux.h"
 #endif
 
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
+#endif
+
 #if defined(OS_WIN)
 #include "ui/native_theme/native_theme_dark_win.h"
 #endif
@@ -221,6 +225,10 @@
 
 void BrowserFrame::OnNativeWidgetWorkspaceChanged() {
   chrome::SaveWindowWorkspace(browser_view_->browser(), GetWorkspace());
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  BrowserList::MoveBrowsersInWorkspaceToFront(
+      views::X11DesktopHandler::get()->GetWorkspace());
+#endif
   Widget::OnNativeWidgetWorkspaceChanged();
 }
 
diff --git a/ui/base/x/x11_util.h b/ui/base/x/x11_util.h
index 0709114..3965a50 100644
--- a/ui/base/x/x11_util.h
+++ b/ui/base/x/x11_util.h
@@ -204,7 +204,7 @@
 static const int kAllDesktops = -1;
 // Queries the desktop |window| is on, kAllDesktops if sticky. Returns false if
 // property not found.
-bool GetWindowDesktop(XID window, int* desktop);
+UI_BASE_X_EXPORT bool GetWindowDesktop(XID window, int* desktop);
 
 // Translates an X11 error code into a printable string.
 UI_BASE_X_EXPORT std::string GetX11ErrorString(XDisplay* display, int err);
diff --git a/ui/views/views.gyp b/ui/views/views.gyp
index 29964e6..5dd8411 100644
--- a/ui/views/views.gyp
+++ b/ui/views/views.gyp
@@ -463,6 +463,7 @@
       'widget/desktop_aura/desktop_window_tree_host_x11.h',
       'widget/desktop_aura/x11_desktop_handler.cc',
       'widget/desktop_aura/x11_desktop_handler.h',
+      'widget/desktop_aura/x11_desktop_handler_observer.h',
       'widget/desktop_aura/x11_desktop_window_move_client.cc',
       'widget/desktop_aura/x11_desktop_window_move_client.h',
       'widget/desktop_aura/x11_move_loop.h',
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 82ba30c..f1cc3f6 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -568,10 +568,20 @@
 }
 
 std::string DesktopWindowTreeHostX11::GetWorkspace() const {
-  int workspace_id;
-  if (ui::GetIntProperty(xwindow_, "_NET_WM_DESKTOP", &workspace_id))
-    return base::IntToString(workspace_id);
-  return std::string();
+  if (workspace_.empty())
+    const_cast<DesktopWindowTreeHostX11*>(this)->UpdateWorkspace();
+  return workspace_;
+}
+
+bool DesktopWindowTreeHostX11::UpdateWorkspace() {
+  int workspace_int;
+  if (!ui::GetWindowDesktop(xwindow_, &workspace_int))
+    return false;
+  std::string workspace_str = base::IntToString(workspace_int);
+  if (workspace_ == workspace_str)
+    return false;
+  workspace_ = workspace_str;
+  return true;
 }
 
 gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInScreen() const {
@@ -1230,6 +1240,7 @@
   if (is_always_on_top_)
     state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_ABOVE"));
 
+  workspace_.clear();
   if (params.visible_on_all_workspaces) {
     state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_STICKY"));
     ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllDesktops);
@@ -1996,12 +2007,14 @@
     }
     case PropertyNotify: {
       ::Atom changed_atom = xev->xproperty.atom;
-      if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE"))
+      if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE")) {
         OnWMStateUpdated();
-      else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS"))
+      } else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS")) {
         OnFrameExtentsUpdated();
-      else if (changed_atom == atom_cache_.GetAtom("_NET_WM_DESKTOP"))
-        OnHostWorkspaceChanged();
+      } else if (changed_atom == atom_cache_.GetAtom("_NET_WM_DESKTOP")) {
+        if (UpdateWorkspace())
+          OnHostWorkspaceChanged();
+      }
       break;
     }
     case SelectionNotify: {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
index fa262f13..074d7776 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h
@@ -185,6 +185,10 @@
   // Called when |xwindow_|'s _NET_FRAME_EXTENTS property is updated.
   void OnFrameExtentsUpdated();
 
+  // Makes a round trip to the X server to get the enclosing workspace for this
+  // window.  Returns true iff |workspace_| was changed.
+  bool UpdateWorkspace();
+
   // Updates |xwindow_|'s minimum and maximum size.
   void UpdateMinAndMaxSize();
 
@@ -284,6 +288,9 @@
   // |xwindow_|'s maximum size.
   gfx::Size max_size_in_pixels_;
 
+  // The workspace containing |xwindow_|.
+  std::string workspace_;
+
   // The window manager state bits.
   std::set< ::Atom> window_properties_;
 
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler.cc b/ui/views/widget/desktop_aura/x11_desktop_handler.cc
index 8ec4b1e..aa3c234 100644
--- a/ui/views/widget/desktop_aura/x11_desktop_handler.cc
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler.cc
@@ -8,6 +8,7 @@
 #include <X11/Xlib.h>
 
 #include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window_event_dispatcher.h"
 #include "ui/base/x/x11_foreign_window_manager.h"
@@ -19,9 +20,10 @@
 
 namespace {
 
-const char* kAtomsToCache[] = {
+const char* const kAtomsToCache[] = {
   "_NET_ACTIVE_WINDOW",
-  NULL
+  "_NET_CURRENT_DESKTOP",
+  nullptr
 };
 
 // Our global instance. Deleted when our Env() is deleted.
@@ -124,6 +126,29 @@
   }
 }
 
+void X11DesktopHandler::AddObserver(X11DesktopHandlerObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void X11DesktopHandler::RemoveObserver(X11DesktopHandlerObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+std::string X11DesktopHandler::GetWorkspace() {
+  if (workspace_.empty())
+    UpdateWorkspace();
+  return workspace_;
+}
+
+bool X11DesktopHandler::UpdateWorkspace() {
+  int desktop;
+  if (ui::GetCurrentDesktop(&desktop)) {
+    workspace_ = base::IntToString(desktop);
+    return true;
+  }
+  return false;
+}
+
 void X11DesktopHandler::set_wm_user_time_ms(Time time_ms) {
   if (time_ms != CurrentTime) {
     int64_t event_time_64 = time_ms;
@@ -184,10 +209,7 @@
 uint32_t X11DesktopHandler::DispatchEvent(const ui::PlatformEvent& event) {
   switch (event->type) {
     case PropertyNotify: {
-      // Check for a change to the active window.
-      CHECK_EQ(x_root_window_, event->xproperty.window);
-      ::Atom active_window_atom = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW");
-      if (event->xproperty.atom == active_window_atom) {
+      if (event->xproperty.atom == atom_cache_.GetAtom("_NET_ACTIVE_WINDOW")) {
         ::Window window;
         if (ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &window) &&
             window) {
@@ -196,10 +218,15 @@
         } else {
           x_active_window_ = None;
         }
+      } else if (event->xproperty.atom ==
+                 atom_cache_.GetAtom("_NET_CURRENT_DESKTOP")) {
+        if (UpdateWorkspace()) {
+          FOR_EACH_OBSERVER(views::X11DesktopHandlerObserver, observers_,
+                            OnWorkspaceChanged(workspace_));
+        }
       }
       break;
     }
-
     case CreateNotify:
       OnWindowCreatedOrDestroyed(event->type, event->xcreatewindow.window);
       break;
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler.h b/ui/views/widget/desktop_aura/x11_desktop_handler.h
index 47a1ff1..b9c9060 100644
--- a/ui/views/widget/desktop_aura/x11_desktop_handler.h
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler.h
@@ -13,12 +13,14 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/observer_list.h"
 #include "ui/aura/env_observer.h"
 #include "ui/events/platform/platform_event_dispatcher.h"
 #include "ui/events/platform/x11/x11_event_source.h"
 #include "ui/gfx/x/x11_atom_cache.h"
 #include "ui/gfx/x/x11_types.h"
 #include "ui/views/views_export.h"
+#include "ui/views/widget/desktop_aura/x11_desktop_handler_observer.h"
 
 namespace base {
 template <typename T> struct DefaultSingletonTraits;
@@ -35,6 +37,13 @@
   // Returns the singleton handler.
   static X11DesktopHandler* get();
 
+  // Adds/removes X11DesktopHandlerObservers.
+  void AddObserver(X11DesktopHandlerObserver* observer);
+  void RemoveObserver(X11DesktopHandlerObserver* observer);
+
+  // Gets the current workspace ID.
+  std::string GetWorkspace();
+
   // Gets/sets the X11 server time of the most recent mouse click, touch or
   // key press on a Chrome window.
   int wm_user_time_ms() const { return wm_user_time_ms_; }
@@ -81,6 +90,9 @@
   // managed by Chrome.
   void OnWindowCreatedOrDestroyed(int event_type, XID window);
 
+  // Makes a round trip to the X server to get the current workspace.
+  bool UpdateWorkspace();
+
   // The display and the native X window hosting the root window.
   XDisplay* xdisplay_;
 
@@ -105,6 +117,10 @@
 
   bool wm_supports_active_window_;
 
+  base::ObserverList<X11DesktopHandlerObserver> observers_;
+
+  std::string workspace_;
+
   DISALLOW_COPY_AND_ASSIGN(X11DesktopHandler);
 };
 
diff --git a/ui/views/widget/desktop_aura/x11_desktop_handler_observer.h b/ui/views/widget/desktop_aura/x11_desktop_handler_observer.h
new file mode 100644
index 0000000..252ca21
--- /dev/null
+++ b/ui/views/widget/desktop_aura/x11_desktop_handler_observer.h
@@ -0,0 +1,26 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_WIDGET_DESKTOP_AURA_X11_DESKTOP_HANDLER_OBSERVER_H_
+#define UI_VIEWS_WIDGET_DESKTOP_AURA_X11_DESKTOP_HANDLER_OBSERVER_H_
+
+#include <string>
+
+#include "ui/views/views_export.h"
+
+namespace views {
+
+class VIEWS_EXPORT X11DesktopHandlerObserver {
+ public:
+  // Called when the (platform-specific) workspace ID changes to
+  // |new_workspace|.
+  virtual void OnWorkspaceChanged(const std::string& new_workspace) = 0;
+
+ protected:
+  virtual ~X11DesktopHandlerObserver() {}
+};
+
+}  // namespace views
+
+#endif  // UI_DISPLAY_DESKTOP_OBSERVER_H_