diff --git a/filenames.gni b/filenames.gni index 70561a49646b..e7d489b3bd25 100644 --- a/filenames.gni +++ b/filenames.gni @@ -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", diff --git a/patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch b/patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch index 648467987266..7f335a88afd4 100644 --- a/patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch +++ b/patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch @@ -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 - - #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& GetMainTaskRunner() { - static base::NoDestructor> - 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 service_started) { -@@ -159,6 +168,12 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() { - } - g_service_availability = ServiceAvailability::kInProgress; +-void OnServiceStarted(std::optional service_started) { ++[[maybe_unused]] void OnServiceStarted(std::optional 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; - diff --git a/shell/browser/ui/file_dialog.h b/shell/browser/ui/file_dialog.h index 5a6c4cadbbf2..b8858c06ecb3 100644 --- a/shell/browser/ui/file_dialog.h +++ b/shell/browser/ui/file_dialog.h @@ -77,6 +77,16 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path); void ShowSaveDialog(const DialogSettings& settings, gin_helper::Promise 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_ diff --git a/shell/browser/ui/file_dialog_linux.cc b/shell/browser/ui/file_dialog_linux.cc index 404aead64b5d..732820aa193a 100644 --- a/shell/browser/ui/file_dialog_linux.cc +++ b/shell/browser/ui/file_dialog_linux.cc @@ -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"; diff --git a/shell/browser/ui/file_dialog_linux_portal.cc b/shell/browser/ui/file_dialog_linux_portal.cc new file mode 100644 index 000000000000..298ee25caffb --- /dev/null +++ b/shell/browser/ui/file_dialog_linux_portal.cc @@ -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 + +#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 version; + + explicit FileChooserProperties(dbus::ObjectProxy* object_proxy) + : dbus::PropertySet(object_proxy, kFileChooserInterfaceName, {}) { + RegisterProperty("version", &version); + } + + ~FileChooserProperties() override = default; +}; + +base::AtomicFlag* GetAvailabilityTestCompletionFlag() { + static base::NoDestructor 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 bus = base::MakeRefCounted(options); + dbus_utils::CheckForServiceAndStart( + bus, kXdgPortalService, + base::BindOnce( + [](scoped_refptr bus, base::AtomicFlag* flag, + std::optional 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