Repository: electron/electron
Pull request: fix: revert required portal version for file chooser dialogs

#### Description of Change

1) Revert the version bump to `3`, to help address https://github.com/electron/electron/issues/43819
2) Provide `xdg-portal-required-version` flag for application developers to control the portal usage (ex: requires support of `current_folder`)
3) If portal code path is used and a call to dialog api with `default_path` is performed then log a warning pointing to the above flag.

#### Release Notes

Notes: fix file chooser dialogs for flaptak applications

Changed files:
filename: docs/api/command-line-switches.md
patch: @@ -241,6 +241,13 @@ Force using discrete GPU when there are multiple GPUs available.
 
 Force using integrated GPU when there are multiple GPUs available.
 
+### --xdg-portal-required-version=`version`
+
+Sets the minimum required version of XDG portal implementation to `version`
+in order to use the portal backend for file dialogs on linux. File dialogs
+will fallback to using gtk or kde depending on the desktop environment when
+the required version is unavailable. Current default is set to `3`.
+
 ## Node.js Flags
 
 Electron supports some of the [CLI flags][node-cli] supported by Node.js.

filename: docs/api/dialog.md
patch: @@ -78,6 +78,11 @@ dialog.showOpenDialogSync(mainWindow, {
 })
 ```
 
+**Note:** On Linux `defaultPath` is not supported when using portal file chooser
+dialogs unless the portal backend is version 4 or higher. You can use `--xdg-portal-required-version`
+[command-line switch](./command-line-switches.md#--xdg-portal-required-versionversion)
+to force gtk or kde dialogs.
+
 ### `dialog.showOpenDialog([window, ]options)`
 
 * `window` [BaseWindow](base-window.md) (optional)
@@ -150,6 +155,11 @@ dialog.showOpenDialog(mainWindow, {
 })
 ```
 
+**Note:** On Linux `defaultPath` is not supported when using portal file chooser
+dialogs unless the portal backend is version 4 or higher. You can use `--xdg-portal-required-version`
+[command-line switch](./command-line-switches.md#--xdg-portal-required-versionversion)
+to force gtk or kde dialogs.
+
 ### `dialog.showSaveDialogSync([window, ]options)`
 
 * `window` [BaseWindow](base-window.md) (optional)

filename: patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch
patch: @@ -199,21 +199,68 @@ index 64a79ebe2e2d21d5a6b4a98042d1cdb7b6edad52..16f2ae01a8d33e6341ed52638e963c34
            &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
            parent));
 diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
-index 143f5fe1028e154192767599a1e68b45301a894d..d612e1614a313db0dcf7dc592fd6fa74c89e70e1 100644
+index 143f5fe1028e154192767599a1e68b45301a894d..132e670dc3ccd9a0f904a8869e516f4556fbf0af 100644
 --- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
 +++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
-@@ -40,7 +40,9 @@ namespace {
+@@ -11,6 +11,7 @@
+ 
+ #include <string_view>
+ 
++#include "base/command_line.h"
+ #include "base/functional/bind.h"
+ #include "base/logging.h"
+ #include "base/no_destructor.h"
+@@ -40,6 +41,8 @@ namespace {
  constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop";
  constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop";
  
--constexpr int kXdgPortalRequiredVersion = 3;
 +// 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 = 4;
+ constexpr int kXdgPortalRequiredVersion = 3;
  
  constexpr char kXdgPortalRequestInterfaceName[] =
-     "org.freedesktop.portal.Request";
-@@ -214,6 +216,8 @@ void SelectFileDialogLinuxPortal::SelectFileImpl(
+@@ -66,6 +69,8 @@ constexpr int kFileChooserFilterKindGlob = 0;
+ 
+ constexpr char kFileUriPrefix[] = "file://";
+ 
++const char kXdgPortalRequiredVersionFlag[] = "xdg-portal-required-version";
++
+ struct FileChooserProperties : dbus::PropertySet {
+   dbus::Property<uint32_t> version;
+ 
+@@ -171,10 +176,18 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
+   if (GetAvailabilityTestCompletionFlag()->IsSet())
+     return;
+ 
++  auto* cmd = base::CommandLine::ForCurrentProcess();
++  unsigned int xdg_portal_required_version;
++  if (!base::StringToUint(cmd->GetSwitchValueASCII(kXdgPortalRequiredVersionFlag),
++                          &xdg_portal_required_version)) {
++    xdg_portal_required_version = kXdgPortalRequiredVersion;
++  }
++
+   dbus_thread_linux::GetTaskRunner()->PostTask(
+       FROM_HERE,
+       base::BindOnce(
+-          &SelectFileDialogLinuxPortal::CheckPortalAvailabilityOnBusThread));
++          &SelectFileDialogLinuxPortal::CheckPortalAvailabilityOnBusThread,
++          xdg_portal_required_version));
+ }
+ 
+ // static
+@@ -185,6 +198,11 @@ bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
+   return is_portal_available_;
+ }
+ 
++// static
++int SelectFileDialogLinuxPortal::GetPortalVersion() {
++  return available_portal_version_;
++}
++
+ // static
+ void SelectFileDialogLinuxPortal::DestroyPortalConnection() {
+   dbus_thread_linux::GetTaskRunner()->PostTask(
+@@ -214,6 +232,8 @@ void SelectFileDialogLinuxPortal::SelectFileImpl(
                       weak_factory_.GetWeakPtr()));
    info_->type = type;
    info_->main_task_runner = base::SequencedTaskRunner::GetCurrentDefault();
@@ -222,7 +269,50 @@ index 143f5fe1028e154192767599a1e68b45301a894d..d612e1614a313db0dcf7dc592fd6fa74
  
    if (owning_window) {
      if (auto* root = owning_window->GetRootWindow()) {
-@@ -471,7 +475,9 @@ void SelectFileDialogLinuxPortal::DialogInfo::AppendOptions(
+@@ -260,7 +280,8 @@ bool SelectFileDialogLinuxPortal::HasMultipleFileTypeChoicesImpl() {
+ }
+ 
+ // static
+-void SelectFileDialogLinuxPortal::CheckPortalAvailabilityOnBusThread() {
++void SelectFileDialogLinuxPortal::CheckPortalAvailabilityOnBusThread(
++    unsigned int xdg_portal_required_version) {
+   DCHECK(dbus_thread_linux::GetTaskRunner()->RunsTasksInCurrentSequence());
+   base::AtomicFlag* availability_test_complete =
+       GetAvailabilityTestCompletionFlag();
+@@ -274,6 +295,7 @@ void SelectFileDialogLinuxPortal::CheckPortalAvailabilityOnBusThread() {
+       base::BindOnce(
+           [](scoped_refptr<dbus::Bus> bus,
+              base::AtomicFlag* availability_test_complete,
++             unsigned int xdg_portal_required_version,
+              std::optional<bool> name_has_owner) {
+             if (name_has_owner.value_or(false)) {
+               // The portal service has an owner, proceed to check the version.
+@@ -285,15 +307,22 @@ void SelectFileDialogLinuxPortal::CheckPortalAvailabilityOnBusThread() {
+               if (!properties.GetAndBlock(&properties.version)) {
+                 LOG(ERROR) << "Failed to read portal version property";
+               } else if (properties.version.value() >=
+-                         kXdgPortalRequiredVersion) {
++                         xdg_portal_required_version) {
+                 is_portal_available_ = true;
++                available_portal_version_ = properties.version.value();
++              } else {
++                VLOG(1) << "File chooser portal available version: "
++                        << properties.version.value();
++                available_portal_version_ = properties.version.value();
+               }
+             }
++            VLOG(1) << "File chooser portal expected version: "
++                    << xdg_portal_required_version;
+             VLOG(1) << "File chooser portal available: "
+                     << (is_portal_available_ ? "yes" : "no");
+             availability_test_complete->Set();
+           },
+-          bus, availability_test_complete));
++          bus, availability_test_complete, xdg_portal_required_version));
+ }
+ 
+ // static
+@@ -471,7 +500,9 @@ void SelectFileDialogLinuxPortal::DialogInfo::AppendOptions(
                       response_handle_token);
  
    if (type == SelectFileDialog::Type::SELECT_UPLOAD_FOLDER) {
@@ -233,7 +323,7 @@ index 143f5fe1028e154192767599a1e68b45301a894d..d612e1614a313db0dcf7dc592fd6fa74
                         l10n_util::GetStringUTF8(
                             IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON));
    }
-@@ -480,6 +486,8 @@ void SelectFileDialogLinuxPortal::DialogInfo::AppendOptions(
+@@ -480,6 +511,8 @@ void SelectFileDialogLinuxPortal::DialogInfo::AppendOptions(
        type == SelectFileDialog::Type::SELECT_UPLOAD_FOLDER ||
        type == SelectFileDialog::Type::SELECT_EXISTING_FOLDER) {
      AppendBoolOption(&options_writer, kFileChooserOptionDirectory, true);
@@ -242,11 +332,29 @@ index 143f5fe1028e154192767599a1e68b45301a894d..d612e1614a313db0dcf7dc592fd6fa74
    } else if (type == SelectFileDialog::Type::SELECT_OPEN_MULTI_FILE) {
      AppendBoolOption(&options_writer, kFileChooserOptionMultiple, true);
    }
+@@ -820,6 +853,7 @@ SelectFileDialogLinuxPortal::DialogInfo::DialogInfo(
+ SelectFileDialogLinuxPortal::DialogInfo::~DialogInfo() = default;
+ 
+ bool SelectFileDialogLinuxPortal::is_portal_available_ = false;
++unsigned int SelectFileDialogLinuxPortal::available_portal_version_ = 0;
+ int SelectFileDialogLinuxPortal::handle_token_counter_ = 0;
+ 
+ }  // namespace ui
 diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.h b/ui/shell_dialogs/select_file_dialog_linux_portal.h
-index d57a52b3ccbd3bd6d390615351ea2ad1e475b157..433f34ac6779611623241cd977dd1214e97fece7 100644
+index d57a52b3ccbd3bd6d390615351ea2ad1e475b157..6a2800add2428ffd91286748f886d6c42510ba31 100644
 --- a/ui/shell_dialogs/select_file_dialog_linux_portal.h
 +++ b/ui/shell_dialogs/select_file_dialog_linux_portal.h
-@@ -120,6 +120,8 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
+@@ -47,6 +47,9 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
+   // availability test has not yet completed.
+   static bool IsPortalAvailable();
+ 
++  // Get version of portal if available.
++  static int GetPortalVersion();
++
+   // Destroys the connection to the bus.
+   static void DestroyPortalConnection();
+ 
+@@ -120,6 +123,8 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
      Type type;
      // The task runner the SelectFileImpl method was called on.
      scoped_refptr<base::SequencedTaskRunner> main_task_runner;
@@ -255,3 +363,23 @@ index d57a52b3ccbd3bd6d390615351ea2ad1e475b157..433f34ac6779611623241cd977dd1214
  
     private:
      friend class base::RefCountedThreadSafe<DialogInfo>;
+@@ -176,7 +181,8 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
+   };
+ 
+   // D-Bus configuration and initialization.
+-  static void CheckPortalAvailabilityOnBusThread();
++  static void CheckPortalAvailabilityOnBusThread(
++      unsigned int xdg_portal_required_version);
+ 
+   // Returns a flag, written by the D-Bus thread and read by the UI thread,
+   // indicating whether or not the availability test has completed.
+@@ -208,6 +214,9 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
+   // Written by the D-Bus thread and read by the UI thread.
+   static bool is_portal_available_;
+ 
++  // Written by the D-Bus thread and read by the UI thread.
++  static unsigned int available_portal_version_;
++
+   // Used by the D-Bus thread to generate unique handle tokens.
+   static int handle_token_counter_;
+ 

filename: shell/browser/ui/file_dialog_linux.cc
patch: @@ -18,6 +18,7 @@
 #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"
 
@@ -58,6 +59,18 @@ ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) {
   return file_type_info;
 }
 
+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()
+              << " does not support defaultPath option, try the non-portal"
+              << " file chooser dialogs by launching with"
+              << " --xdg-portal-required-version";
+  }
+}
+
 class FileChooserDialog : public ui::SelectFileDialog::Listener {
  public:
   enum class DialogType { OPEN, SAVE };
@@ -200,6 +213,7 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
 
 bool ShowOpenDialogSync(const DialogSettings& settings,
                         std::vector<base::FilePath>* paths) {
+  LogIfNeededAboutUnsupportedPortalFeature(settings);
   base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
   auto cb = base::BindOnce(
       [](base::RepeatingClosure cb, std::vector<base::FilePath>* file_paths,
@@ -217,6 +231,7 @@ bool ShowOpenDialogSync(const DialogSettings& settings,
 
 void ShowOpenDialog(const DialogSettings& settings,
                     gin_helper::Promise<gin_helper::Dictionary> promise) {
+  LogIfNeededAboutUnsupportedPortalFeature(settings);
   FileChooserDialog* dialog = new FileChooserDialog();
   dialog->RunOpenDialog(std::move(promise), settings);
 }