Commit 652010dc authored by James Cook's avatar James Cook Committed by Commit Bot

lacros: Fix window stacking for select file dialogs

Select file dialogs are implemented in ash-chrome via
SelectFileDialogExtension, which requires an aura::Window owning window
for the dialog.

Top-level windows in lacros-chrome are represented in ash-chrome via
ShellSurface objects. Pass an ID for the shell surface over mojo from
lacros-chrome to ash-chrome and use it for window parenting.

The ID is a string: "org.chromium.lacros.<unguessable-token>", e.g.
"org.chromium.lacros.8B93BD96131616984961C7790B5E9A62". Lacros creates
an ID for each WaylandToplevelWindow and uses xdg_shell's set_app_id
method to set it. This is similar to how ARC++ encodes an Android
task ID in the app ID, e.g. "org.chromium.arc.12345".

The ID is sent as a string to provide future flexibility (in case we
need to encode different window types, like "lacros_popup") and because
it felt like an appropriate return type for the cross-platform method
ui::PlatformWindow::GetInternalWindowId().

Feature design doc at go/lacros-select-file

Bug: 1090587
Test: multiple simultaneous file open dialogs spawn/stack/close correctly
Change-Id: I7b2cdb934480f72f5dbb3523922d30cf5df0376a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2301119
Commit-Queue: James Cook <jamescook@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarRobert Kroeger <rjkroege@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarLuciano Pacheco <lucmult@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Reviewed-by: default avatarJorge Lucangeli Obes <jorgelo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791992}
parent 6e5f5c33
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include "ash/public/cpp/shell_window_ids.h" #include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/numerics/ranges.h" #include "base/numerics/ranges.h"
...@@ -19,14 +18,10 @@ ...@@ -19,14 +18,10 @@
#include "ui/shell_dialogs/select_file_dialog.h" #include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_policy.h" #include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h" #include "ui/shell_dialogs/selected_file_info.h"
#include "ui/wm/public/activation_client.h"
#include "url/gurl.h" #include "url/gurl.h"
namespace { namespace {
// TODO(https://crbug.com/1090587): Replace with window ID from Wayland client.
int g_next_window_id = 0;
ui::SelectFileDialog::Type GetUiType( ui::SelectFileDialog::Type GetUiType(
crosapi::mojom::SelectFileDialogType type) { crosapi::mojom::SelectFileDialogType type) {
switch (type) { switch (type) {
...@@ -57,36 +52,50 @@ ui::SelectFileDialog::FileTypeInfo::AllowedPaths GetUiAllowedPaths( ...@@ -57,36 +52,50 @@ ui::SelectFileDialog::FileTypeInfo::AllowedPaths GetUiAllowedPaths(
} }
} }
// TODO(https://crbug.com/1090587): Parent to the ShellSurface that spawned // Performs a depth-first search for a window with a given exo ShellSurface
// the dialog. For now, parent to the active window, which in practice should be // |app_id| starting at |root|.
// the spawning window. aura::Window* FindWindowWithShellAppId(aura::Window* root,
aura::Window* GetOwnerWindow() { const std::string& app_id) {
aura::Window* root = ash::Shell::GetRootWindowForNewWindows(); const std::string* id = exo::GetShellApplicationId(root);
aura::Window* active = ::wm::GetActivationClient(root)->GetActiveWindow(); if (id && *id == app_id)
// Check that the active window is still a ShellSurface window. return root;
if (active && exo::GetShellSurfaceBaseForWindow(active)) for (aura::Window* child : root->children()) {
return active; aura::Window* found = FindWindowWithShellAppId(child, app_id);
// Fallback to the active virtual desk. if (found)
return ash::Shell::GetContainer(root, return found;
ash::desks_util::GetActiveDeskContainerId()); }
return nullptr;
}
// Searches all displays for a ShellSurfaceBase with |app_id| and
// returns its aura::Window. Returns null if no such shell surface exists.
aura::Window* GetShellSurfaceWindow(const std::string& app_id) {
for (aura::Window* display_root : ash::Shell::GetAllRootWindows()) {
aura::Window* window = FindWindowWithShellAppId(display_root, app_id);
if (window)
return window;
}
return nullptr;
} }
// Manages a single open/save dialog. There may be multiple dialogs showing at // Manages a single open/save dialog. There may be multiple dialogs showing at
// the same time. Deletes itself when the dialog is closed. // the same time. Deletes itself when the dialog is closed.
class SelectFileDialogHolder : public ui::SelectFileDialog::Listener { class SelectFileDialogHolder : public ui::SelectFileDialog::Listener {
public: public:
SelectFileDialogHolder(crosapi::mojom::SelectFileOptionsPtr options, SelectFileDialogHolder(aura::Window* shell_surface_window,
crosapi::mojom::SelectFileOptionsPtr options,
crosapi::mojom::SelectFile::SelectCallback callback) crosapi::mojom::SelectFile::SelectCallback callback)
: select_callback_(std::move(callback)) { : select_callback_(std::move(callback)) {
DCHECK(shell_surface_window);
// Policy is null because showing the file-dialog-blocked infobar is handled // Policy is null because showing the file-dialog-blocked infobar is handled
// client-side in lacros-chrome. // client-side in lacros-chrome.
select_file_dialog_ = select_file_dialog_ =
SelectFileDialogExtension::Create(this, /*policy=*/nullptr); SelectFileDialogExtension::Create(this, /*policy=*/nullptr);
SelectFileDialogExtension::Owner owner; SelectFileDialogExtension::Owner owner;
owner.window = GetOwnerWindow(); // Parent to the ShellSurface window that spawned the dialog.
// TODO(https://crbug.com/1090587): Replace with ID from Wayland client. owner.window = shell_surface_window;
owner.lacros_window_id = g_next_window_id++; owner.lacros_window_id = options->owning_shell_window_id;
int file_type_index = 0; int file_type_index = 0;
if (options->file_types) { if (options->file_types) {
...@@ -191,6 +200,15 @@ SelectFileCrosapi::~SelectFileCrosapi() = default; ...@@ -191,6 +200,15 @@ SelectFileCrosapi::~SelectFileCrosapi() = default;
void SelectFileCrosapi::Select(crosapi::mojom::SelectFileOptionsPtr options, void SelectFileCrosapi::Select(crosapi::mojom::SelectFileOptionsPtr options,
SelectCallback callback) { SelectCallback callback) {
aura::Window* shell_surface_window =
GetShellSurfaceWindow(options->owning_shell_window_id);
// Bail out if the shell surface doesn't exist any more.
if (!shell_surface_window) {
std::move(callback).Run(
crosapi::mojom::SelectFileResult::kInvalidShellWindow, {}, 0);
return;
}
// Deletes itself when the dialog closes. // Deletes itself when the dialog closes.
new SelectFileDialogHolder(std::move(options), std::move(callback)); new SelectFileDialogHolder(shell_surface_window, std::move(options),
std::move(callback));
} }
...@@ -169,8 +169,9 @@ SelectFileDialogExtension::RoutingID GetRoutingID( ...@@ -169,8 +169,9 @@ SelectFileDialogExtension::RoutingID GetRoutingID(
if (owner.android_task_id.has_value()) if (owner.android_task_id.has_value())
return base::StringPrintf("android.%d", *owner.android_task_id); return base::StringPrintf("android.%d", *owner.android_task_id);
// Lacros ids are already prefixed with "lacros".
if (owner.lacros_window_id.has_value()) if (owner.lacros_window_id.has_value())
return base::StringPrintf("lacros.%d", *owner.lacros_window_id); return *owner.lacros_window_id;
if (web_contents) { if (web_contents) {
return base::StringPrintf( return base::StringPrintf(
......
...@@ -80,7 +80,7 @@ class SelectFileDialogExtension ...@@ -80,7 +80,7 @@ class SelectFileDialogExtension
// Android task ID if the owner window is an Android app. // Android task ID if the owner window is an Android app.
base::Optional<int> android_task_id; base::Optional<int> android_task_id;
// Lacros window ID if the owner window is a Lacros browser. // Lacros window ID if the owner window is a Lacros browser.
base::Optional<int> lacros_window_id; base::Optional<std::string> lacros_window_id;
}; };
void SelectFileWithFileManagerParams(Type type, void SelectFileWithFileManagerParams(Type type,
const base::string16& title, const base::string16& title,
......
...@@ -91,6 +91,9 @@ struct SelectFileOptions { ...@@ -91,6 +91,9 @@ struct SelectFileOptions {
// Optional file type configuration. In the common case for file open (Ctrl-O) // Optional file type configuration. In the common case for file open (Ctrl-O)
// this is null. // this is null.
SelectFileTypeInfo? file_types; SelectFileTypeInfo? file_types;
// Internal window ID for the top-level shell window that owns the dialog.
string owning_shell_window_id;
}; };
// Result of the select file dialog. // Result of the select file dialog.
...@@ -99,7 +102,8 @@ enum SelectFileResult { ...@@ -99,7 +102,8 @@ enum SelectFileResult {
// Everything worked. // Everything worked.
kSuccess = 0, kSuccess = 0,
// TODO(jamescook): Error handling. // Failed because the dialog couldn't find the owning top-level window.
kInvalidShellWindow = 1,
}; };
// Information about the selected file or files. // Information about the selected file or files.
......
...@@ -34,7 +34,9 @@ namespace { ...@@ -34,7 +34,9 @@ namespace {
DEFINE_UI_CLASS_PROPERTY_KEY(Surface*, kMainSurfaceKey, nullptr) DEFINE_UI_CLASS_PROPERTY_KEY(Surface*, kMainSurfaceKey, nullptr)
// Application Id set by the client. // Application Id set by the client. For example:
// "org.chromium.arc.<task-id>" for ARC++ shell surfaces.
// "org.chromium.lacros.<window-id>" for Lacros browser shell surfaces.
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::string, kApplicationIdKey, nullptr) DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::string, kApplicationIdKey, nullptr)
// Startup Id set by the client. // Startup Id set by the client.
......
...@@ -49,6 +49,11 @@ class AURA_EXPORT WindowTreeHostPlatform : public WindowTreeHost, ...@@ -49,6 +49,11 @@ class AURA_EXPORT WindowTreeHostPlatform : public WindowTreeHost,
const gfx::Point& location_in_pixels) override; const gfx::Point& location_in_pixels) override;
void OnCursorVisibilityChangedNative(bool show) override; void OnCursorVisibilityChangedNative(bool show) override;
ui::PlatformWindow* platform_window() { return platform_window_.get(); }
const ui::PlatformWindow* platform_window() const {
return platform_window_.get();
}
protected: protected:
// NOTE: this does not call CreateCompositor(); subclasses must call // NOTE: this does not call CreateCompositor(); subclasses must call
// CreateCompositor() at the appropriate time. // CreateCompositor() at the appropriate time.
...@@ -59,10 +64,6 @@ class AURA_EXPORT WindowTreeHostPlatform : public WindowTreeHost, ...@@ -59,10 +64,6 @@ class AURA_EXPORT WindowTreeHostPlatform : public WindowTreeHost,
void CreateAndSetPlatformWindow(ui::PlatformWindowInitProperties properties); void CreateAndSetPlatformWindow(ui::PlatformWindowInitProperties properties);
void SetPlatformWindow(std::unique_ptr<ui::PlatformWindow> window); void SetPlatformWindow(std::unique_ptr<ui::PlatformWindow> window);
ui::PlatformWindow* platform_window() { return platform_window_.get(); }
const ui::PlatformWindow* platform_window() const {
return platform_window_.get();
}
// ui::PlatformWindowDelegate: // ui::PlatformWindowDelegate:
void OnBoundsChanged(const gfx::Rect& new_bounds) override; void OnBoundsChanged(const gfx::Rect& new_bounds) override;
......
...@@ -138,6 +138,7 @@ source_set("wayland") { ...@@ -138,6 +138,7 @@ source_set("wayland") {
deps = [ deps = [
"//base", "//base",
"//build:lacros_buildflags",
"//build/config/linux/libdrm", "//build/config/linux/libdrm",
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
"//mojo/public/cpp/system", "//mojo/public/cpp/system",
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h" #include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/unguessable_token.h"
#include "build/lacros_buildflags.h"
#include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/hit_test.h" #include "ui/base/hit_test.h"
...@@ -48,7 +50,11 @@ bool WaylandToplevelWindow::CreateShellSurface() { ...@@ -48,7 +50,11 @@ bool WaylandToplevelWindow::CreateShellSurface() {
return false; return false;
} }
shell_surface_->SetAppId(app_id_); #if BUILDFLAG(IS_LACROS)
shell_surface_->SetAppId(window_unique_id_);
#else
shell_surface_->SetAppId(wm_class_class_);
#endif
shell_surface_->SetTitle(window_title_); shell_surface_->SetTitle(window_title_);
SetSizeConstraints(); SetSizeConstraints();
TriggerStateChanges(); TriggerStateChanges();
...@@ -200,6 +206,14 @@ void WaylandToplevelWindow::SizeConstraintsChanged() { ...@@ -200,6 +206,14 @@ void WaylandToplevelWindow::SizeConstraintsChanged() {
SetSizeConstraints(); SetSizeConstraints();
} }
std::string WaylandToplevelWindow::GetWindowUniqueId() const {
#if BUILDFLAG(IS_LACROS)
return window_unique_id_;
#else
return std::string();
#endif
}
void WaylandToplevelWindow::HandleSurfaceConfigure(int32_t width, void WaylandToplevelWindow::HandleSurfaceConfigure(int32_t width,
int32_t height, int32_t height,
bool is_maximized, bool is_maximized,
...@@ -318,7 +332,12 @@ void WaylandToplevelWindow::OnDragSessionClose(uint32_t dnd_action) { ...@@ -318,7 +332,12 @@ void WaylandToplevelWindow::OnDragSessionClose(uint32_t dnd_action) {
bool WaylandToplevelWindow::OnInitialize( bool WaylandToplevelWindow::OnInitialize(
PlatformWindowInitProperties properties) { PlatformWindowInitProperties properties) {
app_id_ = properties.wm_class_class; #if BUILDFLAG(IS_LACROS)
auto token = base::UnguessableToken::Create();
window_unique_id_ = "org.chromium.lacros." + token.ToString();
#else
wm_class_class_ = properties.wm_class_class;
#endif
SetWmMoveLoopHandler(this, static_cast<WmMoveLoopHandler*>(this)); SetWmMoveLoopHandler(this, static_cast<WmMoveLoopHandler*>(this));
return true; return true;
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_TOPLEVEL_WINDOW_H_ #ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_TOPLEVEL_WINDOW_H_
#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_TOPLEVEL_WINDOW_H_ #define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_TOPLEVEL_WINDOW_H_
#include "build/lacros_buildflags.h"
#include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/geometry/vector2d.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h" #include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/platform_window/platform_window_handler/wm_drag_handler.h" #include "ui/platform_window/platform_window_handler/wm_drag_handler.h"
...@@ -56,6 +57,7 @@ class WaylandToplevelWindow : public WaylandWindow, ...@@ -56,6 +57,7 @@ class WaylandToplevelWindow : public WaylandWindow,
void Restore() override; void Restore() override;
PlatformWindowState GetPlatformWindowState() const override; PlatformWindowState GetPlatformWindowState() const override;
void SizeConstraintsChanged() override; void SizeConstraintsChanged() override;
std::string GetWindowUniqueId() const override;
private: private:
// WaylandWindow overrides: // WaylandWindow overrides:
...@@ -114,11 +116,17 @@ class WaylandToplevelWindow : public WaylandWindow, ...@@ -114,11 +116,17 @@ class WaylandToplevelWindow : public WaylandWindow,
bool is_active_ = false; bool is_active_ = false;
#if BUILDFLAG(IS_LACROS)
// Unique ID for this window. May be shared over non-Wayland IPC transports
// (e.g. mojo) to identify the window.
std::string window_unique_id_;
#else
// Id of the chromium app passed through // Id of the chromium app passed through
// PlatformWindowInitProperties::wm_class_class. This is used by Wayland // PlatformWindowInitProperties::wm_class_class. This is used by Wayland
// compositor to identify the app, unite it's windows into the same stack of // compositor to identify the app, unite it's windows into the same stack of
// windows and find *.desktop file to set various preferences including icons. // windows and find *.desktop file to set various preferences including icons.
std::string app_id_; std::string wm_class_class_;
#endif
// Title of the ShellSurface. // Title of the ShellSurface.
base::string16 window_title_; base::string16 window_title_;
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "ui/platform_window/platform_window.h" #include "ui/platform_window/platform_window.h"
#include <string>
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
namespace ui { namespace ui {
...@@ -48,4 +50,8 @@ void PlatformWindow::SetOpacity(float opacity) {} ...@@ -48,4 +50,8 @@ void PlatformWindow::SetOpacity(float opacity) {}
void PlatformWindow::SetVisibilityChangedAnimationsEnabled(bool enabled) {} void PlatformWindow::SetVisibilityChangedAnimationsEnabled(bool enabled) {}
std::string PlatformWindow::GetWindowUniqueId() const {
return std::string();
}
} // namespace ui } // namespace ui
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define UI_PLATFORM_WINDOW_PLATFORM_WINDOW_H_ #define UI_PLATFORM_WINDOW_PLATFORM_WINDOW_H_
#include <memory> #include <memory>
#include <string>
#include "base/component_export.h" #include "base/component_export.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
...@@ -140,6 +141,10 @@ class COMPONENT_EXPORT(PLATFORM_WINDOW) PlatformWindow ...@@ -140,6 +141,10 @@ class COMPONENT_EXPORT(PLATFORM_WINDOW) PlatformWindow
// Enables or disables platform provided animations of the PlatformWindow. // Enables or disables platform provided animations of the PlatformWindow.
// If |enabled| is set to false, animations are disabled. // If |enabled| is set to false, animations are disabled.
virtual void SetVisibilityChangedAnimationsEnabled(bool enabled); virtual void SetVisibilityChangedAnimationsEnabled(bool enabled);
// Returns a unique ID for the window. The interpretation of the ID is
// platform specific. Overriding this method is optional.
virtual std::string GetWindowUniqueId() const;
}; };
} // namespace ui } // namespace ui
......
...@@ -118,6 +118,7 @@ jumbo_component("shell_dialogs") { ...@@ -118,6 +118,7 @@ jumbo_component("shell_dialogs") {
"//chromeos/crosapi/mojom", "//chromeos/crosapi/mojom",
"//chromeos/lacros", "//chromeos/lacros",
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
"//ui/platform_window",
] ]
} }
} }
......
...@@ -8,5 +8,6 @@ include_rules = [ ...@@ -8,5 +8,6 @@ include_rules = [
"+ui/aura", "+ui/aura",
"+ui/base", "+ui/base",
"+ui/gfx", "+ui/gfx",
"+ui/platform_window",
"+ui/strings/grit/ui_strings.h", "+ui/strings/grit/ui_strings.h",
] ]
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
#include "base/notreached.h" #include "base/notreached.h"
#include "chromeos/crosapi/mojom/select_file.mojom.h" #include "chromeos/crosapi/mojom/select_file.mojom.h"
#include "chromeos/lacros/lacros_chrome_service_impl.h" #include "chromeos/lacros/lacros_chrome_service_impl.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host_platform.h"
#include "ui/platform_window/platform_window.h"
#include "ui/shell_dialogs/select_file_policy.h" #include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h" #include "ui/shell_dialogs/selected_file_info.h"
...@@ -58,6 +61,18 @@ SelectedFileInfo ConvertSelectedFileInfo( ...@@ -58,6 +61,18 @@ SelectedFileInfo ConvertSelectedFileInfo(
return file; return file;
} }
// Returns the ID of the Wayland shell surface that contains |window|.
std::string GetShellWindowUniqueId(aura::Window* window) {
// On desktop aura there is one WindowTreeHost per top-level window.
aura::WindowTreeHost* window_tree_host = window->GetRootWindow()->GetHost();
DCHECK(window_tree_host);
// Lacros is based on Ozone/Wayland, which uses PlatformWindow and
// aura::WindowTreeHostPlatform.
aura::WindowTreeHostPlatform* window_tree_host_platform =
static_cast<aura::WindowTreeHostPlatform*>(window_tree_host);
return window_tree_host_platform->platform_window()->GetWindowUniqueId();
}
} // namespace } // namespace
SelectFileDialogLacros::Factory::Factory() = default; SelectFileDialogLacros::Factory::Factory() = default;
...@@ -111,6 +126,7 @@ void SelectFileDialogLacros::SelectFileImpl( ...@@ -111,6 +126,7 @@ void SelectFileDialogLacros::SelectFileImpl(
options->file_types->allowed_paths = options->file_types->allowed_paths =
GetMojoAllowedPaths(file_types->allowed_paths); GetMojoAllowedPaths(file_types->allowed_paths);
} }
options->owning_shell_window_id = GetShellWindowUniqueId(owning_window);
// Send request to ash-chrome. // Send request to ash-chrome.
chromeos::LacrosChromeServiceImpl::Get()->select_file_remote()->Select( chromeos::LacrosChromeServiceImpl::Get()->select_file_remote()->Select(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment