Browse Source

refactor: use `//ui/shell_dialogs` on Linux (#42109)

trop[bot] 11 months ago
parent
commit
72d85e90ac

+ 4 - 11
BUILD.gn

@@ -81,18 +81,11 @@ if (is_linux) {
     ]
   }
 
-  # Generates electron_gtk_stubs.h header which contains
-  # stubs for extracting function ptrs from the gtk library.
-  # Function signatures for which stubs are required should be
-  # declared in electron_gtk.sigs, currently this file contains
-  # signatures for the functions used with native file chooser
-  # implementation. In future, this file can be extended to contain
-  # gtk4 stubs to switch gtk version in runtime.
+  # Generates headers which contain stubs for extracting function ptrs
+  # from the gtk library. Function signatures for which stubs are
+  # required should be declared in the sig files.
   generate_stubs("electron_gtk_stubs") {
-    sigs = [
-      "shell/browser/ui/electron_gdk_pixbuf.sigs",
-      "shell/browser/ui/electron_gtk.sigs",
-    ]
+    sigs = [ "shell/browser/ui/electron_gdk_pixbuf.sigs" ]
     extra_header = "shell/browser/ui/electron_gtk.fragment"
     output_name = "electron_gtk_stubs"
     public_deps = [ "//ui/gtk:gtk_config" ]

+ 1 - 1
filenames.gni

@@ -34,7 +34,7 @@ filenames = {
     "shell/browser/notifications/linux/notification_presenter_linux.h",
     "shell/browser/relauncher_linux.cc",
     "shell/browser/ui/electron_desktop_window_tree_host_linux.cc",
-    "shell/browser/ui/file_dialog_gtk.cc",
+    "shell/browser/ui/file_dialog_linux.cc",
     "shell/browser/ui/gtk/menu_gtk.cc",
     "shell/browser/ui/gtk/menu_gtk.h",
     "shell/browser/ui/gtk/menu_util.cc",

+ 1 - 0
patches/chromium/.patches

@@ -129,3 +129,4 @@ fix_add_support_for_skipping_first_2_no-op_refreshes_in_thumb_cap.patch
 refactor_expose_file_system_access_blocklist.patch
 revert_power_update_trace_counter_in_power_monitor.patch
 cherry-pick-b2cc7b7ac538.patch
+feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch

+ 243 - 0
patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch

@@ -0,0 +1,243 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shelley Vohr <[email protected]>
+Date: Sun, 5 May 2024 09:17:17 +0000
+Subject: feat: add support for missing dialog features to //shell_dialogs
+
+This CL adds support for the following features to //shell_dialogs:
+* buttonLabel - Custom label for the confirmation button.
+* showHiddenFiles - Show hidden files in dialog.
+* showOverwriteConfirmation - Whether the user will be presented a confirmation dialog if the user types a file name that already exists.
+
+This may be partially upstreamed to Chromium in the future.
+
+diff --git a/ui/gtk/select_file_dialog_linux_gtk.cc b/ui/gtk/select_file_dialog_linux_gtk.cc
+index 698f97b23f851c02af037880585c82d4494da63f..0a3b701a701289a2124214e7421a6383ff7f0320 100644
+--- a/ui/gtk/select_file_dialog_linux_gtk.cc
++++ b/ui/gtk/select_file_dialog_linux_gtk.cc
+@@ -243,6 +243,10 @@ void SelectFileDialogLinuxGtk::SelectFileImpl(
+ 
+   std::string title_string = base::UTF16ToUTF8(title);
+ 
++  ExtraSettings extra_settings;
++  if (params)
++    extra_settings = *(static_cast<ExtraSettings*>(params));
++
+   set_file_type_index(file_type_index);
+   if (file_types)
+     set_file_types(*file_types);
+@@ -261,23 +265,23 @@ void SelectFileDialogLinuxGtk::SelectFileImpl(
+     case SELECT_UPLOAD_FOLDER:
+     case SELECT_EXISTING_FOLDER:
+       dialog = CreateSelectFolderDialog(type, title_string, default_path,
+-                                        owning_window);
++                                        owning_window, extra_settings);
+       connect("response",
+               &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse);
+       break;
+     case SELECT_OPEN_FILE:
+-      dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
++      dialog = CreateFileOpenDialog(title_string, default_path, owning_window, extra_settings);
+       connect("response",
+               &SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse);
+       break;
+     case SELECT_OPEN_MULTI_FILE:
+       dialog =
+-          CreateMultiFileOpenDialog(title_string, default_path, owning_window);
++          CreateMultiFileOpenDialog(title_string, default_path, owning_window, extra_settings);
+       connect("response",
+               &SelectFileDialogLinuxGtk::OnSelectMultiFileDialogResponse);
+       break;
+     case SELECT_SAVEAS_FILE:
+-      dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
++      dialog = CreateSaveAsDialog(title_string, default_path, owning_window, extra_settings);
+       connect("response",
+               &SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse);
+       break;
+@@ -412,10 +416,14 @@ void SelectFileDialogLinuxGtk::FileNotSelected(GtkWidget* dialog) {
+ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
+     const std::string& title,
+     const base::FilePath& default_path,
+-    gfx::NativeWindow parent) {
++    gfx::NativeWindow parent,
++    const ExtraSettings& settings) {
++  const char* button_label = settings.button_label.empty()
++                                 ? GetOpenLabel()
++                                 : settings.button_label.c_str();
+   GtkWidget* dialog = GtkFileChooserDialogNew(
+       title.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, GetCancelLabel(),
+-      GTK_RESPONSE_CANCEL, GetOpenLabel(), GTK_RESPONSE_ACCEPT);
++      GTK_RESPONSE_CANCEL, button_label, GTK_RESPONSE_ACCEPT);
+   SetGtkTransientForAura(dialog, parent);
+   AddFilters(GTK_FILE_CHOOSER(dialog));
+ 
+@@ -431,6 +439,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
+     GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog),
+                                    *last_opened_path());
+   }
++  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog),
++                                   settings.show_hidden);
+   return dialog;
+ }
+ 
+@@ -438,7 +448,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+     Type type,
+     const std::string& title,
+     const base::FilePath& default_path,
+-    gfx::NativeWindow parent) {
++    gfx::NativeWindow parent,
++    const ExtraSettings& settings) {
+   std::string title_string = title;
+   if (title_string.empty()) {
+     title_string =
+@@ -446,11 +457,14 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+             ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE)
+             : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
+   }
+-  std::string accept_button_label =
+-      (type == SELECT_UPLOAD_FOLDER)
+-          ? l10n_util::GetStringUTF8(
+-                IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
+-          : GetOpenLabel();
++
++  std::string accept_button_label = settings.button_label;
++  if (accept_button_label.empty()) {
++    accept_button_label = (type == SELECT_UPLOAD_FOLDER)
++        ? l10n_util::GetStringUTF8(
++              IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
++        : GetOpenLabel();
++  }
+ 
+   GtkWidget* dialog = GtkFileChooserDialogNew(
+       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+@@ -472,19 +486,21 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+   gtk_file_filter_add_mime_type(only_folders, "inode/directory");
+   gtk_file_filter_add_mime_type(only_folders, "text/directory");
+   gtk_file_chooser_add_filter(chooser, only_folders);
+-  gtk_file_chooser_set_select_multiple(chooser, FALSE);
++  gtk_file_chooser_set_select_multiple(chooser, settings.allow_multiple_selection);
++  gtk_file_chooser_set_show_hidden(chooser, settings.show_hidden);
+   return dialog;
+ }
+ 
+ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenDialog(
+     const std::string& title,
+     const base::FilePath& default_path,
+-    gfx::NativeWindow parent) {
++    gfx::NativeWindow parent,
++    const ExtraSettings& settings) {
+   std::string title_string =
+       !title.empty() ? title
+                      : l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
+ 
+-  GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
++  GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent, settings);
+   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+   return dialog;
+ }
+@@ -492,12 +508,14 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenDialog(
+ GtkWidget* SelectFileDialogLinuxGtk::CreateMultiFileOpenDialog(
+     const std::string& title,
+     const base::FilePath& default_path,
+-    gfx::NativeWindow parent) {
++    gfx::NativeWindow parent,
++    const ExtraSettings& settings) {
+   std::string title_string =
+       !title.empty() ? title
+                      : l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
+ 
+-  GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
++  GtkWidget* dialog =
++      CreateFileOpenHelper(title_string, default_path, parent, settings);
+   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+   return dialog;
+ }
+@@ -505,14 +523,17 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateMultiFileOpenDialog(
+ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
+     const std::string& title,
+     const base::FilePath& default_path,
+-    gfx::NativeWindow parent) {
++    gfx::NativeWindow parent,
++    const ExtraSettings& settings) {
+   std::string title_string =
+       !title.empty() ? title
+                      : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
+-
++  const char* button_label = settings.button_label.empty()
++                                 ? GetSaveLabel()
++                                 : settings.button_label.c_str();
+   GtkWidget* dialog = GtkFileChooserDialogNew(
+       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SAVE,
+-      GetCancelLabel(), GTK_RESPONSE_CANCEL, GetSaveLabel(),
++      GetCancelLabel(), GTK_RESPONSE_CANCEL, button_label,
+       GTK_RESPONSE_ACCEPT);
+   SetGtkTransientForAura(dialog, parent);
+ 
+@@ -538,9 +559,10 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
+   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
+   // Overwrite confirmation is always enabled in GTK4.
+   if (!GtkCheckVersion(4)) {
+-    gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
+-                                                   TRUE);
++    gtk_file_chooser_set_do_overwrite_confirmation(
++        GTK_FILE_CHOOSER(dialog), settings.show_overwrite_confirmation);
+   }
++  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), settings.show_hidden);
+   return dialog;
+ }
+ 
+diff --git a/ui/gtk/select_file_dialog_linux_gtk.h b/ui/gtk/select_file_dialog_linux_gtk.h
+index 53ae15f14c45ee72abdae172fc4555c9e4b3ff9a..af181afd9db1351cd886ba24dd651c7bf2f8a716 100644
+--- a/ui/gtk/select_file_dialog_linux_gtk.h
++++ b/ui/gtk/select_file_dialog_linux_gtk.h
+@@ -15,6 +15,13 @@
+ 
+ namespace gtk {
+ 
++struct ExtraSettings {
++  std::string button_label;
++  bool show_overwrite_confirmation = true;
++  bool show_hidden = false;
++  bool allow_multiple_selection = false;
++};
++
+ // Implementation of SelectFileDialog that shows a Gtk common dialog for
+ // choosing a file or folder. This acts as a modal dialog.
+ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
+@@ -90,19 +97,23 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
+   GtkWidget* CreateSelectFolderDialog(Type type,
+                                       const std::string& title,
+                                       const base::FilePath& default_path,
+-                                      gfx::NativeWindow parent);
++                                      gfx::NativeWindow parent,
++                                      const ExtraSettings& settings);
+ 
+   GtkWidget* CreateFileOpenDialog(const std::string& title,
+                                   const base::FilePath& default_path,
+-                                  gfx::NativeWindow parent);
++                                  gfx::NativeWindow parent,
++                                  const ExtraSettings& settings);
+ 
+   GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
+                                        const base::FilePath& default_path,
+-                                       gfx::NativeWindow parent);
++                                       gfx::NativeWindow parent,
++                                       const ExtraSettings& settings);
+ 
+   GtkWidget* CreateSaveAsDialog(const std::string& title,
+                                 const base::FilePath& default_path,
+-                                gfx::NativeWindow parent);
++                                gfx::NativeWindow parent,
++                                const ExtraSettings& settings);
+ 
+   // Removes and returns the |params| associated with |dialog| from
+   // |params_map_|.
+@@ -121,7 +132,8 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
+   // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
+   GtkWidget* CreateFileOpenHelper(const std::string& title,
+                                   const base::FilePath& default_path,
+-                                  gfx::NativeWindow parent);
++                                  gfx::NativeWindow parent,
++                                  const ExtraSettings& settings);
+ 
+   // Callback for when the user responds to a Save As or Open File dialog.
+   void OnSelectSingleFileDialogResponse(GtkWidget* dialog, int response_id);

+ 7 - 29
patches/chromium/make_gtk_getlibgtk_public.patch

@@ -1,13 +1,13 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: deepak1556 <[email protected]>
 Date: Thu, 7 Apr 2022 20:30:16 +0900
-Subject: Make gtk::GetLibGtk and gtk::GetLibGdkPixbuf public
+Subject: Make gtk::GetLibGdkPixbuf public
 
-Allows embedders to get a handle to the gtk and
-gdk_pixbuf libraries already loaded in the process.
+Allows embedders to get a handle to the gdk_pixbuf
+library already loaded in the process.
 
 diff --git a/ui/gtk/gtk_compat.cc b/ui/gtk/gtk_compat.cc
-index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411dbcbd96457 100644
+index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..e410998c4197c98947d2e1ad8bebe12c70379358 100644
 --- a/ui/gtk/gtk_compat.cc
 +++ b/ui/gtk/gtk_compat.cc
 @@ -66,11 +66,6 @@ void* GetLibGio() {
@@ -22,20 +22,7 @@ index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411db
  void* GetLibGdk3() {
    static void* libgdk3 = DlOpen("libgdk-3.so.0");
    return libgdk3;
-@@ -86,12 +81,6 @@ void* GetLibGtk4(bool check = true) {
-   return libgtk4;
- }
- 
--void* GetLibGtk() {
--  if (GtkCheckVersion(4))
--    return GetLibGtk4();
--  return GetLibGtk3();
--}
--
- bool LoadGtk3() {
-   if (!GetLibGtk3(false))
-     return false;
-@@ -134,6 +123,17 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
+@@ -134,6 +129,11 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
  
  }  // namespace
  
@@ -43,29 +30,20 @@ index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411db
 +  static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
 +  return libgdk_pixbuf;
 +}
-+
-+void* GetLibGtk() {
-+  if (GtkCheckVersion(4))
-+    return GetLibGtk4();
-+  return GetLibGtk3();
-+}
 +
  bool LoadGtk() {
    static bool loaded = LoadGtkImpl();
    return loaded;
 diff --git a/ui/gtk/gtk_compat.h b/ui/gtk/gtk_compat.h
-index 19f73cc179d82a3729c5fe37883460ac05f4d0c3..cdb67c98291f145f89f76a50b31bf00318648518 100644
+index 19f73cc179d82a3729c5fe37883460ac05f4d0c3..17aa0b95bd6158ed02c03095c1687185a057fe62 100644
 --- a/ui/gtk/gtk_compat.h
 +++ b/ui/gtk/gtk_compat.h
-@@ -41,6 +41,12 @@ using SkColor = uint32_t;
+@@ -41,6 +41,9 @@ using SkColor = uint32_t;
  
  namespace gtk {
  
 +// Get handle to the currently loaded gdk_pixbuf library in the process.
 +void* GetLibGdkPixbuf();
-+
-+// Get handle to the currently loaded gtk library in the process.
-+void* GetLibGtk();
 +
  // Loads libgtk and related libraries and returns true on success.
  bool LoadGtk();

+ 0 - 6
shell/browser/electron_browser_main_parts.cc

@@ -398,12 +398,6 @@ void ElectronBrowserMainParts::ToolkitInitialized() {
   CHECK(linux_ui);
   linux_ui_getter_ = std::make_unique<LinuxUiGetterImpl>();
 
-  // Try loading gtk symbols used by Electron.
-  electron::InitializeElectron_gtk(gtk::GetLibGtk());
-  if (!electron::IsElectron_gtkInitialized()) {
-    electron::UninitializeElectron_gtk();
-  }
-
   electron::InitializeElectron_gdk_pixbuf(gtk::GetLibGdkPixbuf());
   CHECK(electron::IsElectron_gdk_pixbufInitialized())
       << "Failed to initialize libgdk_pixbuf-2.0.so.0";

+ 0 - 395
shell/browser/ui/file_dialog_gtk.cc

@@ -1,395 +0,0 @@
-// Copyright (c) 2014 GitHub, Inc.
-// Use of this source code is governed by the MIT license that can be
-// found in the LICENSE file.
-
-#include <memory>
-#include <string>
-
-#include "base/files/file_util.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/raw_ptr_exclusion.h"
-#include "base/strings/string_util.h"
-#include "electron/electron_gtk_stubs.h"
-#include "shell/browser/javascript_environment.h"
-#include "shell/browser/native_window_views.h"
-#include "shell/browser/ui/file_dialog.h"
-#include "shell/browser/ui/gtk_util.h"
-#include "shell/common/gin_converters/file_path_converter.h"
-#include "shell/common/thread_restrictions.h"
-#include "ui/base/glib/scoped_gsignal.h"
-#include "ui/gtk/gtk_ui.h"    // nogncheck
-#include "ui/gtk/gtk_util.h"  // nogncheck
-
-namespace file_dialog {
-
-DialogSettings::DialogSettings() = default;
-DialogSettings::DialogSettings(const DialogSettings&) = default;
-DialogSettings::~DialogSettings() = default;
-
-namespace {
-
-static const int kPreviewWidth = 256;
-static const int kPreviewHeight = 512;
-
-std::string MakeCaseInsensitivePattern(const std::string& extension) {
-  // If the extension is the "all files" extension, no change needed.
-  if (extension == "*")
-    return extension;
-
-  std::string pattern("*.");
-  for (char ch : extension) {
-    if (!base::IsAsciiAlpha(ch)) {
-      pattern.push_back(ch);
-      continue;
-    }
-
-    pattern.push_back('[');
-    pattern.push_back(base::ToLowerASCII(ch));
-    pattern.push_back(base::ToUpperASCII(ch));
-    pattern.push_back(']');
-  }
-
-  return pattern;
-}
-
-class FileChooserDialog {
- public:
-  FileChooserDialog(GtkFileChooserAction action, const DialogSettings& settings)
-      : parent_(
-            static_cast<electron::NativeWindowViews*>(settings.parent_window)),
-        filters_(settings.filters) {
-    auto label = settings.button_label;
-
-    if (electron::IsElectron_gtkInitialized()) {
-      dialog_ = GTK_FILE_CHOOSER(gtk_file_chooser_native_new(
-          settings.title.c_str(), nullptr, action,
-          label.empty() ? nullptr : label.c_str(), nullptr));
-    } else {
-      const char* confirm_text = gtk_util::GetOkLabel();
-      if (!label.empty())
-        confirm_text = label.c_str();
-      else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
-        confirm_text = gtk_util::GetSaveLabel();
-      else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
-        confirm_text = gtk_util::GetOpenLabel();
-
-      dialog_ = GTK_FILE_CHOOSER(gtk_file_chooser_dialog_new(
-          settings.title.c_str(), nullptr, action, gtk_util::GetCancelLabel(),
-          GTK_RESPONSE_CANCEL, confirm_text, GTK_RESPONSE_ACCEPT, nullptr));
-    }
-
-    if (parent_) {
-      parent_->SetEnabled(false);
-      if (electron::IsElectron_gtkInitialized()) {
-        gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(dialog_), TRUE);
-      } else {
-        gtk::SetGtkTransientForAura(GTK_WIDGET(dialog_),
-                                    parent_->GetNativeWindow());
-        gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
-      }
-    }
-
-    if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
-      gtk_file_chooser_set_do_overwrite_confirmation(dialog_, TRUE);
-    if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
-      gtk_file_chooser_set_create_folders(dialog_, TRUE);
-
-    if (!settings.default_path.empty()) {
-      electron::ScopedAllowBlockingForElectron allow_blocking;
-      if (base::DirectoryExists(settings.default_path)) {
-        gtk_file_chooser_set_current_folder(
-            dialog_, settings.default_path.value().c_str());
-      } else {
-        if (settings.default_path.IsAbsolute()) {
-          gtk_file_chooser_set_current_folder(
-              dialog_, settings.default_path.DirName().value().c_str());
-        }
-
-        gtk_file_chooser_set_current_name(
-            GTK_FILE_CHOOSER(dialog_),
-            settings.default_path.BaseName().value().c_str());
-      }
-    }
-
-    if (!settings.filters.empty())
-      AddFilters(settings.filters);
-
-    // GtkFileChooserNative does not support preview widgets through the
-    // org.freedesktop.portal.FileChooser portal. In the case of running through
-    // the org.freedesktop.portal.FileChooser portal, anything having to do with
-    // the update-preview signal or the preview widget will just be ignored.
-    if (!electron::IsElectron_gtkInitialized()) {
-      preview_ = gtk_image_new();
-      signals_.emplace_back(
-          dialog_, "update-preview",
-          base::BindRepeating(&FileChooserDialog::OnUpdatePreview,
-                              base::Unretained(this)));
-      gtk_file_chooser_set_preview_widget(dialog_, preview_);
-    }
-  }
-
-  ~FileChooserDialog() {
-    if (electron::IsElectron_gtkInitialized()) {
-      gtk_native_dialog_destroy(GTK_NATIVE_DIALOG(dialog_));
-    } else {
-      gtk_widget_destroy(GTK_WIDGET(dialog_));
-    }
-
-    if (parent_)
-      parent_->SetEnabled(true);
-  }
-
-  // disable copy
-  FileChooserDialog(const FileChooserDialog&) = delete;
-  FileChooserDialog& operator=(const FileChooserDialog&) = delete;
-
-  void SetupOpenProperties(int properties) {
-    const auto hasProp = [properties](OpenFileDialogProperty prop) {
-      return gboolean((properties & prop) != 0);
-    };
-    auto* file_chooser = dialog();
-    gtk_file_chooser_set_select_multiple(file_chooser,
-                                         hasProp(OPEN_DIALOG_MULTI_SELECTIONS));
-    gtk_file_chooser_set_show_hidden(file_chooser,
-                                     hasProp(OPEN_DIALOG_SHOW_HIDDEN_FILES));
-  }
-
-  void SetupSaveProperties(int properties) {
-    const auto hasProp = [properties](SaveFileDialogProperty prop) {
-      return gboolean((properties & prop) != 0);
-    };
-    auto* file_chooser = dialog();
-    gtk_file_chooser_set_show_hidden(file_chooser,
-                                     hasProp(SAVE_DIALOG_SHOW_HIDDEN_FILES));
-    gtk_file_chooser_set_do_overwrite_confirmation(
-        file_chooser, hasProp(SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION));
-  }
-
-  void RunAsynchronous() {
-    signals_.emplace_back(
-        GTK_WIDGET(dialog_), "response",
-        base::BindRepeating(&FileChooserDialog::OnFileDialogResponse,
-                            base::Unretained(this)));
-    if (electron::IsElectron_gtkInitialized()) {
-      gtk_native_dialog_show(GTK_NATIVE_DIALOG(dialog_));
-    } else {
-      gtk_widget_show_all(GTK_WIDGET(dialog_));
-      gtk::GtkUi::GetPlatform()->ShowGtkWindow(GTK_WINDOW(dialog_));
-    }
-  }
-
-  void RunSaveAsynchronous(
-      gin_helper::Promise<gin_helper::Dictionary> promise) {
-    save_promise_ =
-        std::make_unique<gin_helper::Promise<gin_helper::Dictionary>>(
-            std::move(promise));
-    RunAsynchronous();
-  }
-
-  void RunOpenAsynchronous(
-      gin_helper::Promise<gin_helper::Dictionary> promise) {
-    open_promise_ =
-        std::make_unique<gin_helper::Promise<gin_helper::Dictionary>>(
-            std::move(promise));
-    RunAsynchronous();
-  }
-
-  base::FilePath GetFileName() const {
-    gchar* filename = gtk_file_chooser_get_filename(dialog_);
-    const base::FilePath path(filename);
-    g_free(filename);
-    return path;
-  }
-
-  std::vector<base::FilePath> GetFileNames() const {
-    std::vector<base::FilePath> paths;
-    auto* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_));
-    for (auto* iter = filenames; iter != nullptr; iter = iter->next) {
-      auto* filename = static_cast<char*>(iter->data);
-      paths.emplace_back(filename);
-      g_free(filename);
-    }
-    g_slist_free(filenames);
-    return paths;
-  }
-
-  void OnFileDialogResponse(GtkWidget* widget, int response);
-
-  GtkFileChooser* dialog() const { return dialog_; }
-
- private:
-  void AddFilters(const Filters& filters);
-
-  raw_ptr<electron::NativeWindowViews> parent_;
-
-  RAW_PTR_EXCLUSION GtkFileChooser* dialog_;
-  RAW_PTR_EXCLUSION GtkWidget* preview_;
-
-  Filters filters_;
-  std::unique_ptr<gin_helper::Promise<gin_helper::Dictionary>> save_promise_;
-  std::unique_ptr<gin_helper::Promise<gin_helper::Dictionary>> open_promise_;
-
-  // Callback for when we update the preview for the selection.
-  void OnUpdatePreview(GtkFileChooser* chooser);
-
-  std::vector<ScopedGSignal> signals_;
-};
-
-void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
-  if (electron::IsElectron_gtkInitialized()) {
-    gtk_native_dialog_hide(GTK_NATIVE_DIALOG(dialog_));
-  } else {
-    gtk_widget_hide(GTK_WIDGET(dialog_));
-  }
-  v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
-  v8::HandleScope scope(isolate);
-  if (save_promise_) {
-    auto dict = gin_helper::Dictionary::CreateEmpty(save_promise_->isolate());
-    if (response == GTK_RESPONSE_ACCEPT) {
-      dict.Set("canceled", false);
-      dict.Set("filePath", GetFileName());
-    } else {
-      dict.Set("canceled", true);
-      dict.Set("filePath", base::FilePath());
-    }
-    save_promise_->Resolve(dict);
-  } else if (open_promise_) {
-    auto dict = gin_helper::Dictionary::CreateEmpty(open_promise_->isolate());
-    if (response == GTK_RESPONSE_ACCEPT) {
-      dict.Set("canceled", false);
-      dict.Set("filePaths", GetFileNames());
-    } else {
-      dict.Set("canceled", true);
-      dict.Set("filePaths", std::vector<base::FilePath>());
-    }
-    open_promise_->Resolve(dict);
-  }
-  delete this;
-}
-
-void FileChooserDialog::AddFilters(const Filters& filters) {
-  for (const auto& filter : filters) {
-    GtkFileFilter* gtk_filter = gtk_file_filter_new();
-
-    for (const auto& extension : filter.second) {
-      std::string pattern = MakeCaseInsensitivePattern(extension);
-      gtk_file_filter_add_pattern(gtk_filter, pattern.c_str());
-    }
-
-    gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
-    gtk_file_chooser_add_filter(dialog_, gtk_filter);
-  }
-}
-
-bool CanPreview(const struct stat& st) {
-  // Only preview regular files; pipes may hang.
-  // See https://crbug.com/534754.
-  if (!S_ISREG(st.st_mode)) {
-    return false;
-  }
-
-  // Don't preview huge files; they may crash.
-  // https://github.com/electron/electron/issues/31630
-  // Setting an arbitrary filesize max t at 100 MB here.
-  constexpr off_t ArbitraryMax = 100000000ULL;
-  return st.st_size < ArbitraryMax;
-}
-
-void FileChooserDialog::OnUpdatePreview(GtkFileChooser* chooser) {
-  CHECK(!electron::IsElectron_gtkInitialized());
-  gchar* filename = gtk_file_chooser_get_preview_filename(chooser);
-  if (!filename) {
-    gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
-    return;
-  }
-
-  struct stat sb;
-  if (stat(filename, &sb) != 0 || !CanPreview(sb)) {
-    g_free(filename);
-    gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
-    return;
-  }
-
-  // This will preserve the image's aspect ratio.
-  GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
-                                                       kPreviewHeight, nullptr);
-  g_free(filename);
-  if (pixbuf) {
-    gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
-    g_object_unref(pixbuf);
-  }
-  gtk_file_chooser_set_preview_widget_active(chooser, pixbuf ? TRUE : FALSE);
-}
-
-}  // namespace
-
-void ShowFileDialog(const FileChooserDialog& dialog) {
-  // gtk_native_dialog_run() will call gtk_native_dialog_show() for us.
-  if (!electron::IsElectron_gtkInitialized()) {
-    gtk_widget_show_all(GTK_WIDGET(dialog.dialog()));
-  }
-}
-
-int RunFileDialog(const FileChooserDialog& dialog) {
-  int response = 0;
-  if (electron::IsElectron_gtkInitialized()) {
-    response = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.dialog()));
-  } else {
-    response = gtk_dialog_run(GTK_DIALOG(dialog.dialog()));
-  }
-
-  return response;
-}
-
-bool ShowOpenDialogSync(const DialogSettings& settings,
-                        std::vector<base::FilePath>* paths) {
-  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
-  if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
-    action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
-  FileChooserDialog open_dialog(action, settings);
-  open_dialog.SetupOpenProperties(settings.properties);
-
-  ShowFileDialog(open_dialog);
-
-  const int response = RunFileDialog(open_dialog);
-  if (response == GTK_RESPONSE_ACCEPT) {
-    *paths = open_dialog.GetFileNames();
-    return true;
-  }
-  return false;
-}
-
-void ShowOpenDialog(const DialogSettings& settings,
-                    gin_helper::Promise<gin_helper::Dictionary> promise) {
-  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
-  if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
-    action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
-  FileChooserDialog* open_dialog = new FileChooserDialog(action, settings);
-  open_dialog->SetupOpenProperties(settings.properties);
-  open_dialog->RunOpenAsynchronous(std::move(promise));
-}
-
-bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
-  FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
-  save_dialog.SetupSaveProperties(settings.properties);
-
-  ShowFileDialog(save_dialog);
-
-  const int response = RunFileDialog(save_dialog);
-  if (response == GTK_RESPONSE_ACCEPT) {
-    *path = save_dialog.GetFileName();
-    return true;
-  }
-  return false;
-}
-
-void ShowSaveDialog(const DialogSettings& settings,
-                    gin_helper::Promise<gin_helper::Dictionary> promise) {
-  FileChooserDialog* save_dialog =
-      new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
-  save_dialog->RunSaveAsynchronous(std::move(promise));
-}
-
-}  // namespace file_dialog

+ 258 - 0
shell/browser/ui/file_dialog_linux.cc

@@ -0,0 +1,258 @@
+// Copyright (c) 2024 Microsoft, GmbH.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ptr_exclusion.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "shell/browser/javascript_environment.h"
+#include "shell/browser/native_window_views.h"
+#include "shell/browser/ui/file_dialog.h"
+#include "shell/common/gin_converters/callback_converter.h"
+#include "shell/common/gin_converters/file_path_converter.h"
+#include "ui/gtk/select_file_dialog_linux_gtk.h"  // nogncheck
+#include "ui/shell_dialogs/select_file_dialog.h"
+#include "ui/shell_dialogs/selected_file_info.h"
+
+namespace file_dialog {
+
+DialogSettings::DialogSettings() = default;
+DialogSettings::DialogSettings(const DialogSettings&) = default;
+DialogSettings::~DialogSettings() = default;
+
+namespace {
+
+ui::SelectFileDialog::Type GetDialogType(int properties) {
+  if (properties & OPEN_DIALOG_OPEN_DIRECTORY)
+    return ui::SelectFileDialog::SELECT_FOLDER;
+
+  if (properties & OPEN_DIALOG_MULTI_SELECTIONS)
+    return ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
+
+  return ui::SelectFileDialog::SELECT_OPEN_FILE;
+}
+
+ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) {
+  ui::SelectFileDialog::FileTypeInfo file_type_info;
+
+  for (const auto& [name, extension_group] : filters) {
+    file_type_info.extension_description_overrides.push_back(
+        base::UTF8ToUTF16(name));
+
+    const bool has_all_files_wildcard = base::ranges::any_of(
+        extension_group, [](const auto& ext) { return ext == "*"; });
+    if (has_all_files_wildcard) {
+      file_type_info.include_all_files = true;
+    } else {
+      file_type_info.extensions.emplace_back(extension_group);
+    }
+  }
+
+  return file_type_info;
+}
+
+class FileChooserDialog : public ui::SelectFileDialog::Listener {
+ public:
+  enum class DialogType { OPEN, SAVE };
+
+  FileChooserDialog() { dialog_ = ui::SelectFileDialog::Create(this, nullptr); }
+
+  ~FileChooserDialog() override = default;
+
+  gtk::ExtraSettings GetExtraSettings(const DialogSettings& settings) {
+    gtk::ExtraSettings extra;
+    extra.button_label = settings.button_label;
+    extra.show_overwrite_confirmation =
+        settings.properties & SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION;
+    extra.allow_multiple_selection =
+        settings.properties & OPEN_DIALOG_MULTI_SELECTIONS;
+    if (type_ == DialogType::SAVE) {
+      extra.show_hidden = settings.properties & SAVE_DIALOG_SHOW_HIDDEN_FILES;
+    } else {
+      extra.show_hidden = settings.properties & OPEN_DIALOG_SHOW_HIDDEN_FILES;
+    }
+
+    return extra;
+  }
+
+  void RunSaveDialogImpl(const DialogSettings& settings) {
+    type_ = DialogType::SAVE;
+    ui::SelectFileDialog::FileTypeInfo file_info =
+        GetFilterInfo(settings.filters);
+    auto extra_settings = GetExtraSettings(settings);
+    dialog_->SelectFile(
+        ui::SelectFileDialog::SELECT_SAVEAS_FILE,
+        base::UTF8ToUTF16(settings.title), settings.default_path,
+        &file_info /* file_types */, 0 /* file_type_index */,
+        base::FilePath::StringType() /* default_extension */,
+        settings.parent_window ? settings.parent_window->GetNativeWindow()
+                               : nullptr,
+        static_cast<void*>(&extra_settings));
+  }
+
+  void RunSaveDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
+                     const DialogSettings& settings) {
+    promise_ = std::move(promise);
+    RunSaveDialogImpl(settings);
+  }
+
+  void RunSaveDialog(base::OnceCallback<void(gin_helper::Dictionary)> callback,
+                     const DialogSettings& settings) {
+    callback_ = std::move(callback);
+    RunSaveDialogImpl(settings);
+  }
+
+  void RunOpenDialogImpl(const DialogSettings& settings) {
+    type_ = DialogType::OPEN;
+    ui::SelectFileDialog::FileTypeInfo file_info =
+        GetFilterInfo(settings.filters);
+    auto extra_settings = GetExtraSettings(settings);
+    dialog_->SelectFile(
+        GetDialogType(settings.properties), base::UTF8ToUTF16(settings.title),
+        settings.default_path, &file_info, 0 /* file_type_index */,
+        base::FilePath::StringType() /* default_extension */,
+        settings.parent_window ? settings.parent_window->GetNativeWindow()
+                               : nullptr,
+        static_cast<void*>(&extra_settings));
+  }
+
+  void RunOpenDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
+                     const DialogSettings& settings) {
+    promise_ = std::move(promise);
+    RunOpenDialogImpl(settings);
+  }
+
+  void RunOpenDialog(base::OnceCallback<void(gin_helper::Dictionary)> callback,
+                     const DialogSettings& settings) {
+    callback_ = std::move(callback);
+    RunOpenDialogImpl(settings);
+  }
+
+  void FileSelected(const ui::SelectedFileInfo& file,
+                    int index,
+                    void* params) override {
+    v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
+    v8::HandleScope scope(isolate);
+    auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
+    dict.Set("canceled", false);
+    if (type_ == DialogType::SAVE) {
+      dict.Set("filePath", file.file_path);
+    } else {
+      dict.Set("filePaths", std::vector<base::FilePath>{file.file_path});
+    }
+
+    if (callback_) {
+      std::move(callback_).Run(dict);
+    } else {
+      promise_.Resolve(dict);
+    }
+
+    delete this;
+  }
+
+  void MultiFilesSelected(const std::vector<ui::SelectedFileInfo>& files,
+                          void* params) override {
+    v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
+    v8::HandleScope scope(isolate);
+    auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
+    dict.Set("canceled", false);
+    dict.Set("filePaths", ui::SelectedFileInfoListToFilePathList(files));
+
+    if (callback_) {
+      std::move(callback_).Run(dict);
+    } else {
+      promise_.Resolve(dict);
+    }
+
+    delete this;
+  }
+
+  void FileSelectionCanceled(void* params) override {
+    v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
+    v8::HandleScope scope(isolate);
+    auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
+    dict.Set("canceled", true);
+    if (type_ == DialogType::SAVE) {
+      dict.Set("filePath", base::FilePath());
+    } else {
+      dict.Set("filePaths", std::vector<base::FilePath>());
+    }
+
+    if (callback_) {
+      std::move(callback_).Run(dict);
+    } else {
+      promise_.Resolve(dict);
+    }
+
+    delete this;
+  }
+
+ private:
+  DialogType type_;
+  scoped_refptr<ui::SelectFileDialog> dialog_;
+  base::OnceCallback<void(gin_helper::Dictionary)> callback_;
+  gin_helper::Promise<gin_helper::Dictionary> promise_;
+};
+
+}  // namespace
+
+bool ShowOpenDialogSync(const DialogSettings& settings,
+                        std::vector<base::FilePath>* paths) {
+  v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
+  gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
+
+  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+  auto cb = base::BindOnce(
+      [](base::RepeatingClosure cb, std::vector<base::FilePath>* file_paths,
+         gin_helper::Dictionary result) {
+        result.Get("filePaths", file_paths);
+        std::move(cb).Run();
+      },
+      run_loop.QuitClosure(), paths);
+
+  FileChooserDialog* dialog = new FileChooserDialog();
+  dialog->RunOpenDialog(std::move(cb), settings);
+
+  run_loop.Run();
+  return !paths->empty();
+}
+
+void ShowOpenDialog(const DialogSettings& settings,
+                    gin_helper::Promise<gin_helper::Dictionary> promise) {
+  FileChooserDialog* dialog = new FileChooserDialog();
+  dialog->RunOpenDialog(std::move(promise), settings);
+}
+
+bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
+  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+  v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
+  gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
+  auto cb = base::BindOnce(
+      [](base::RepeatingClosure cb, base::FilePath* file_path,
+         gin_helper::Dictionary result) {
+        result.Get("filePath", file_path);
+        std::move(cb).Run();
+      },
+      run_loop.QuitClosure(), path);
+
+  FileChooserDialog* dialog = new FileChooserDialog();
+  dialog->RunSaveDialog(std::move(promise), settings);
+  run_loop.Run();
+  return !path->empty();
+}
+
+void ShowSaveDialog(const DialogSettings& settings,
+                    gin_helper::Promise<gin_helper::Dictionary> promise) {
+  FileChooserDialog* dialog = new FileChooserDialog();
+  dialog->RunSaveDialog(std::move(promise), settings);
+}
+
+}  // namespace file_dialog