fix: xdg portal version detection for file dialogs on linux (#46936)

chore: use dbus thread for portal version detection
This commit is contained in:
Robo 2025-05-06 04:34:00 +09:00 committed by GitHub
parent 800640ea2d
commit cd871fd58c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 172 additions and 86 deletions

View file

@ -35,6 +35,7 @@ filenames = {
"shell/browser/relauncher_linux.cc",
"shell/browser/ui/electron_desktop_window_tree_host_linux.cc",
"shell/browser/ui/file_dialog_linux.cc",
"shell/browser/ui/file_dialog_linux_portal.cc",
"shell/browser/ui/gtk/menu_gtk.cc",
"shell/browser/ui/gtk/menu_gtk.h",
"shell/browser/ui/gtk/menu_util.cc",

View file

@ -10,6 +10,8 @@ This CL adds support for the following features to //shell_dialogs:
It also:
* Changes XDG Portal implementation behavior to set default path regardless of dialog type.
* XDG Portal implementation calls into //electron to perform version checks on the dbus thread
Refs https://github.com/electron/electron/issues/46652.
This may be partially upstreamed to Chromium in the future.
@ -345,83 +347,52 @@ index 9d45ec49a4fb5e12407b65b83c1ba0c13cd0dfd8..400cce91b020ecd5e48566f125515d2c
+
} // namespace ui
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea0396a49e 100644
index b6a116654ef6815e3d97dd9302d2a9930877dda8..cdc929d9538ae22ad8b9fa4044e4fc6ee326564b 100644
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
@@ -7,6 +7,7 @@
#include <string_view>
#include "base/check.h"
+#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
@@ -40,6 +41,8 @@ namespace {
constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop";
constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop";
+// Version 4 includes support for current_folder option to the OpenFile method via
+// https://github.com/flatpak/xdg-desktop-portal/commit/71165a5.
constexpr int kXdgPortalRequiredVersion = 3;
constexpr char kFileChooserInterfaceName[] =
@@ -61,6 +64,8 @@ constexpr uint32_t kFileChooserFilterKindGlob = 0;
constexpr char kFileUriPrefix[] = "file://";
+const char kXdgPortalRequiredVersionFlag[] = "xdg-portal-required-version";
+
enum class ServiceAvailability {
kNotStarted,
kInProgress,
@@ -70,6 +75,9 @@ enum class ServiceAvailability {
ServiceAvailability g_service_availability = ServiceAvailability::kNotStarted;
+uint32_t g_available_portal_version = 0;
+uint32_t g_required_portal_version = kXdgPortalRequiredVersion;
+
scoped_refptr<base::SequencedTaskRunner>& GetMainTaskRunner() {
static base::NoDestructor<scoped_refptr<base::SequencedTaskRunner>>
main_task_runner;
@@ -89,9 +97,10 @@ void OnGetPropertyReply(dbus::Response* response) {
return;
}
- g_service_availability = version >= kXdgPortalRequiredVersion
+ g_service_availability = version >= g_required_portal_version
? ServiceAvailability::kAvailable
@@ -23,6 +23,7 @@
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
+#include "electron/shell/browser/ui/file_dialog.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/native_widget_types.h"
@@ -94,7 +95,7 @@ void OnGetPropertyReply(dbus::Response* response) {
: ServiceAvailability::kNotAvailable;
+ g_available_portal_version = version;
}
void OnServiceStarted(std::optional<bool> service_started) {
@@ -159,6 +168,12 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
}
g_service_availability = ServiceAvailability::kInProgress;
-void OnServiceStarted(std::optional<bool> service_started) {
+[[maybe_unused]] void OnServiceStarted(std::optional<bool> service_started) {
if (!service_started.value_or(false)) {
g_service_availability = ServiceAvailability::kNotAvailable;
return;
@@ -161,18 +162,24 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
+ auto* cmd = base::CommandLine::ForCurrentProcess();
+ if (!base::StringToUint(cmd->GetSwitchValueASCII(kXdgPortalRequiredVersionFlag),
+ &g_required_portal_version)) {
+ g_required_portal_version = kXdgPortalRequiredVersion;
+ }
+
GetMainTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault();
+#if 0
dbus_utils::CheckForServiceAndStart(dbus_thread_linux::GetSharedSessionBus(),
@@ -175,6 +190,11 @@ bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
return g_service_availability == ServiceAvailability::kAvailable;
kXdgPortalService,
base::BindOnce(&OnServiceStarted));
+#endif
+ file_dialog::StartPortalAvailabilityTestInBackground();
}
// static
bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
+#if 0
if (g_service_availability == ServiceAvailability::kInProgress) {
LOG(WARNING) << "Portal availability checked before test was complete";
}
return g_service_availability == ServiceAvailability::kAvailable;
+#endif
+ return file_dialog::IsPortalAvailable();
}
+// static
+uint32_t SelectFileDialogLinuxPortal::GetPortalVersion() {
+ return g_available_portal_version;
+}
+
bool SelectFileDialogLinuxPortal::IsRunning(
gfx::NativeWindow parent_window) const {
return parent_window && host_ && host_.get() == parent_window->GetHost();
@@ -377,11 +397,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
@@ -377,11 +384,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
const PortalFilterSet& filter_set) {
DbusDictionary dict;
@ -439,7 +410,7 @@ index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea
[[fallthrough]];
case SelectFileDialog::SELECT_FOLDER:
case SelectFileDialog::Type::SELECT_EXISTING_FOLDER:
@@ -394,6 +417,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
@@ -394,6 +404,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
break;
}
@ -450,17 +421,3 @@ index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea
if (!default_path.empty()) {
if (default_path_exists) {
// If this is an existing directory, navigate to that directory, with no
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.h b/ui/shell_dialogs/select_file_dialog_linux_portal.h
index 651684b1840eaff664f3d73d99bbea40e097c866..9a9d541f1e9586d9d545f8547d3f09ff33dce48d 100644
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.h
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.h
@@ -45,6 +45,9 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
// availability test has not yet completed.
static bool IsPortalAvailable();
+ // Get version of portal if available.
+ static uint32_t GetPortalVersion();
+
protected:
~SelectFileDialogLinuxPortal() override;

View file

@ -77,6 +77,16 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path);
void ShowSaveDialog(const DialogSettings& settings,
gin_helper::Promise<gin_helper::Dictionary> promise);
#if BUILDFLAG(IS_LINUX)
// Rewrite of SelectFileDialogLinuxPortal equivalent functions with primary
// difference being that dbus_thread_linux::GetSharedSessionBus is not used
// so that version detection can be initiated and compeleted on the dbus thread
// Refs https://github.com/electron/electron/issues/46652
void StartPortalAvailabilityTestInBackground();
bool IsPortalAvailable();
uint32_t GetPortalVersion();
#endif
} // namespace file_dialog
#endif // ELECTRON_SHELL_BROWSER_UI_FILE_DIALOG_H_

View file

@ -18,7 +18,6 @@
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_dialog_linux_portal.h"
#include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h"
@ -60,11 +59,9 @@ ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) {
}
void LogIfNeededAboutUnsupportedPortalFeature(const DialogSettings& settings) {
if (!settings.default_path.empty() &&
ui::SelectFileDialogLinuxPortal::IsPortalAvailable() &&
ui::SelectFileDialogLinuxPortal::GetPortalVersion() < 4) {
LOG(INFO) << "Available portal version "
<< ui::SelectFileDialogLinuxPortal::GetPortalVersion()
if (!settings.default_path.empty() && IsPortalAvailable() &&
GetPortalVersion() < 4) {
LOG(INFO) << "Available portal version " << GetPortalVersion()
<< " does not support defaultPath option, try the non-portal"
<< " file chooser dialogs by launching with"
<< " --xdg-portal-required-version";

View file

@ -0,0 +1,121 @@
// Copyright (c) 2025 Microsoft, GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/file_dialog.h"
#include <optional>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/atomic_flag.h"
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "components/dbus/utils/check_for_service_and_start.h"
#include "dbus/bus.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "dbus/property.h"
namespace file_dialog {
namespace {
constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop";
constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop";
constexpr char kFileChooserInterfaceName[] =
"org.freedesktop.portal.FileChooser";
// Version 4 includes support for current_folder option to the OpenFile method
// via https://github.com/flatpak/xdg-desktop-portal/commit/71165a5.
uint32_t g_required_portal_version = 3;
uint32_t g_available_portal_version = 0;
constexpr char kXdgPortalRequiredVersionFlag[] = "xdg-portal-required-version";
bool g_portal_available = false;
struct FileChooserProperties : dbus::PropertySet {
dbus::Property<uint32_t> version;
explicit FileChooserProperties(dbus::ObjectProxy* object_proxy)
: dbus::PropertySet(object_proxy, kFileChooserInterfaceName, {}) {
RegisterProperty("version", &version);
}
~FileChooserProperties() override = default;
};
base::AtomicFlag* GetAvailabilityTestCompletionFlag() {
static base::NoDestructor<base::AtomicFlag> flag;
return flag.get();
}
void CheckPortalAvailabilityOnBusThread() {
auto* flag = GetAvailabilityTestCompletionFlag();
if (flag->IsSet())
return;
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SESSION;
options.connection_type = dbus::Bus::PRIVATE;
options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
scoped_refptr<dbus::Bus> bus = base::MakeRefCounted<dbus::Bus>(options);
dbus_utils::CheckForServiceAndStart(
bus, kXdgPortalService,
base::BindOnce(
[](scoped_refptr<dbus::Bus> bus, base::AtomicFlag* flag,
std::optional<bool> name_has_owner) {
if (name_has_owner.value_or(false)) {
//
dbus::ObjectPath portal_path(kXdgPortalObject);
dbus::ObjectProxy* portal =
bus->GetObjectProxy(kXdgPortalService, portal_path);
FileChooserProperties properties(portal);
if (!properties.GetAndBlock(&properties.version)) {
LOG(ERROR) << "Failed to read portal version property";
} else if (properties.version.value() >=
g_required_portal_version) {
g_portal_available = true;
g_available_portal_version = properties.version.value();
}
}
VLOG(1) << "File chooser portal available: "
<< (g_portal_available ? "yes" : "no");
flag->Set();
bus->ShutdownAndBlock();
},
std::move(bus), flag));
}
} // namespace
void StartPortalAvailabilityTestInBackground() {
if (GetAvailabilityTestCompletionFlag()->IsSet())
return;
const auto* cmd = base::CommandLine::ForCurrentProcess();
if (!base::StringToUint(
cmd->GetSwitchValueASCII(kXdgPortalRequiredVersionFlag),
&g_required_portal_version)) {
VLOG(1) << "Unable to parse --xdg-portal-required-version";
}
dbus_thread_linux::GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&CheckPortalAvailabilityOnBusThread));
}
bool IsPortalAvailable() {
if (!GetAvailabilityTestCompletionFlag()->IsSet())
LOG(WARNING) << "Portal availability checked before test was complete";
return g_portal_available;
}
uint32_t GetPortalVersion() {
return g_available_portal_version;
}
} // namespace file_dialog