1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #include "mozilla/Types.h"
13 #include "AsyncDBus.h"
14 #include "nsGtkUtils.h"
15 #include "nsIFileURL.h"
16 #include "nsIGIOService.h"
18 #include "nsIWidget.h"
20 #include "nsIStringBundle.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/Preferences.h"
23 #include "mozilla/dom/Promise.h"
25 #include "nsArrayEnumerator.h"
26 #include "nsEnumeratorUtils.h"
27 #include "nsNetUtil.h"
28 #include "nsReadableUtils.h"
29 #include "MozContainer.h"
30 #include "WidgetUtilsGtk.h"
32 #include "nsFilePicker.h"
36 # include "mozilla/Logging.h"
37 # include "nsTArray.h"
39 extern mozilla::LazyLogModule gWidgetLog
;
40 # define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
43 #endif /* MOZ_LOGGING */
45 using namespace mozilla
;
46 using mozilla::dom::Promise
;
48 #define MAX_PREVIEW_SIZE 180
50 #define MAX_PREVIEW_SOURCE_SIZE 4096
52 nsIFile
* nsFilePicker::mPrevDisplayDirectory
= nullptr;
54 void nsFilePicker::Shutdown() { NS_IF_RELEASE(mPrevDisplayDirectory
); }
56 static GtkFileChooserAction
GetGtkFileChooserAction(nsIFilePicker::Mode aMode
) {
57 GtkFileChooserAction action
;
60 case nsIFilePicker::modeSave
:
61 action
= GTK_FILE_CHOOSER_ACTION_SAVE
;
64 case nsIFilePicker::modeGetFolder
:
65 action
= GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
;
68 case nsIFilePicker::modeOpen
:
69 case nsIFilePicker::modeOpenMultiple
:
70 action
= GTK_FILE_CHOOSER_ACTION_OPEN
;
74 NS_WARNING("Unknown nsIFilePicker mode");
75 action
= GTK_FILE_CHOOSER_ACTION_OPEN
;
82 static void UpdateFilePreviewWidget(GtkFileChooser
* file_chooser
,
83 gpointer preview_widget_voidptr
) {
84 GtkImage
* preview_widget
= GTK_IMAGE(preview_widget_voidptr
);
85 char* image_filename
= gtk_file_chooser_get_preview_filename(file_chooser
);
88 if (!image_filename
) {
89 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
93 gint preview_width
= 0;
94 gint preview_height
= 0;
96 * if file is named pipe, Open is blocking which may lead to UI
97 * nonresponsiveness; if file is directory/socket, it also isn't
98 * likely to get preview */
99 if (stat(image_filename
, &st_buf
) || (!S_ISREG(st_buf
.st_mode
))) {
100 g_free(image_filename
);
101 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
102 return; /* stat failed or file is not regular */
105 GdkPixbufFormat
* preview_format
=
106 gdk_pixbuf_get_file_info(image_filename
, &preview_width
, &preview_height
);
107 if (!preview_format
|| preview_width
<= 0 || preview_height
<= 0 ||
108 preview_width
> MAX_PREVIEW_SOURCE_SIZE
||
109 preview_height
> MAX_PREVIEW_SOURCE_SIZE
) {
110 g_free(image_filename
);
111 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
115 GdkPixbuf
* preview_pixbuf
= nullptr;
116 // Only scale down images that are too big
117 if (preview_width
> MAX_PREVIEW_SIZE
|| preview_height
> MAX_PREVIEW_SIZE
) {
118 preview_pixbuf
= gdk_pixbuf_new_from_file_at_size(
119 image_filename
, MAX_PREVIEW_SIZE
, MAX_PREVIEW_SIZE
, nullptr);
121 preview_pixbuf
= gdk_pixbuf_new_from_file(image_filename
, nullptr);
124 g_free(image_filename
);
126 if (!preview_pixbuf
) {
127 gtk_file_chooser_set_preview_widget_active(file_chooser
, FALSE
);
131 GdkPixbuf
* preview_pixbuf_temp
= preview_pixbuf
;
132 preview_pixbuf
= gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp
);
133 g_object_unref(preview_pixbuf_temp
);
135 // This is the easiest way to do center alignment without worrying about
136 // containers Minimum 3px padding each side (hence the 6) just to make things
139 (MAX_PREVIEW_SIZE
+ 6 - gdk_pixbuf_get_width(preview_pixbuf
)) / 2;
140 gtk_misc_set_padding(GTK_MISC(preview_widget
), x_padding
, 0);
142 gtk_image_set_from_pixbuf(preview_widget
, preview_pixbuf
);
143 g_object_unref(preview_pixbuf
);
144 gtk_file_chooser_set_preview_widget_active(file_chooser
, TRUE
);
147 static nsAutoCString
MakeCaseInsensitiveShellGlob(const char* aPattern
) {
149 nsAutoCString result
;
150 unsigned int len
= strlen(aPattern
);
152 for (unsigned int i
= 0; i
< len
; i
++) {
153 if (!g_ascii_isalpha(aPattern
[i
])) {
154 // non-ASCII characters will also trigger this path, so unicode
155 // is safely handled albeit case-sensitively
156 result
.Append(aPattern
[i
]);
160 // add the lowercase and uppercase version of a character to a bracket
161 // match, so it matches either the lowercase or uppercase char.
163 result
.Append(g_ascii_tolower(aPattern
[i
]));
164 result
.Append(g_ascii_toupper(aPattern
[i
]));
171 NS_IMPL_ISUPPORTS(nsFilePicker
, nsIFilePicker
)
173 nsFilePicker::nsFilePicker()
174 : mSelectedType(0), mAllowURLs(false), mFileChooserDelegate(nullptr) {
175 mUseNativeFileChooser
=
176 widget::ShouldUsePortal(widget::PortalKind::FilePicker
);
179 nsFilePicker::~nsFilePicker() = default;
181 void ReadMultipleFiles(gpointer filename
, gpointer array
) {
182 nsCOMPtr
<nsIFile
> localfile
;
184 NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename
)),
185 getter_AddRefs(localfile
));
186 if (NS_SUCCEEDED(rv
)) {
187 nsCOMArray
<nsIFile
>& files
= *static_cast<nsCOMArray
<nsIFile
>*>(array
);
188 files
.AppendObject(localfile
);
194 void nsFilePicker::ReadValuesFromFileChooser(void* file_chooser
) {
197 if (mMode
== nsIFilePicker::modeOpenMultiple
) {
201 gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser
));
202 g_slist_foreach(list
, ReadMultipleFiles
, static_cast<gpointer
>(&mFiles
));
205 gchar
* filename
= gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser
));
206 mFileURL
.Assign(filename
);
210 GtkFileFilter
* filter
=
211 gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser
));
212 GSList
* filter_list
=
213 gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser
));
215 mSelectedType
= static_cast<int16_t>(g_slist_index(filter_list
, filter
));
216 g_slist_free(filter_list
);
218 // Remember last used directory.
219 nsCOMPtr
<nsIFile
> file
;
220 GetFile(getter_AddRefs(file
));
222 nsCOMPtr
<nsIFile
> dir
;
223 file
->GetParent(getter_AddRefs(dir
));
225 dir
.swap(mPrevDisplayDirectory
);
230 void nsFilePicker::InitNative(nsIWidget
* aParent
, const nsAString
& aTitle
) {
231 mParentWidget
= aParent
;
232 mTitle
.Assign(aTitle
);
236 nsFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
237 Promise
** aRetPromise
) {
238 #ifdef MOZ_ENABLE_DBUS
239 if (!widget::ShouldUsePortal(widget::PortalKind::FilePicker
) ||
240 aMode
!= nsIFilePicker::modeGetFolder
) {
241 return nsBaseFilePicker::IsModeSupported(aMode
, aCx
, aRetPromise
);
244 const char kFreedesktopPortalName
[] = "org.freedesktop.portal.Desktop";
245 const char kFreedesktopPortalPath
[] = "/org/freedesktop/portal/desktop";
246 const char kFreedesktopPortalFileChooser
[] =
247 "org.freedesktop.portal.FileChooser";
250 MOZ_ASSERT(aRetPromise
);
252 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
253 if (NS_WARN_IF(!globalObject
)) {
254 return NS_ERROR_FAILURE
;
258 RefPtr
<Promise
> retPromise
= Promise::Create(globalObject
, result
);
259 if (NS_WARN_IF(result
.Failed())) {
260 return result
.StealNSResult();
263 widget::CreateDBusProxyForBus(
265 GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
),
266 /* aInterfaceInfo = */ nullptr, kFreedesktopPortalName
,
267 kFreedesktopPortalPath
, kFreedesktopPortalFileChooser
)
269 GetCurrentSerialEventTarget(), __func__
,
270 [retPromise
](RefPtr
<GDBusProxy
>&& aProxy
) {
271 const char kFreedesktopPortalVersionProperty
[] = "version";
272 // Folder selection was added in version 3 of xdg-desktop-portal
273 const uint32_t kFreedesktopPortalMinimumVersion
= 3;
274 uint32_t foundVersion
= 0;
276 RefPtr
<GVariant
> property
=
277 dont_AddRef(g_dbus_proxy_get_cached_property(
278 aProxy
, kFreedesktopPortalVersionProperty
));
281 foundVersion
= g_variant_get_uint32(property
);
282 LOG(("Found portal version: %u", foundVersion
));
285 retPromise
->MaybeResolve(foundVersion
>=
286 kFreedesktopPortalMinimumVersion
);
288 [retPromise
](GUniquePtr
<GError
>&& aError
) {
289 g_printerr("Failed to create DBUS proxy: %s\n", aError
->message
);
290 retPromise
->MaybeReject(NS_ERROR_FAILURE
);
293 retPromise
.forget(aRetPromise
);
296 return nsBaseFilePicker::IsModeSupported(aMode
, aCx
, aRetPromise
);
301 nsFilePicker::AppendFilters(int32_t aFilterMask
) {
302 mAllowURLs
= !!(aFilterMask
& filterAllowURLs
);
303 return nsBaseFilePicker::AppendFilters(aFilterMask
);
307 nsFilePicker::AppendFilter(const nsAString
& aTitle
, const nsAString
& aFilter
) {
308 if (aFilter
.EqualsLiteral("..apps")) {
309 // No platform specific thing we can do here, really....
313 nsAutoCString filter
, name
;
314 CopyUTF16toUTF8(aFilter
, filter
);
315 CopyUTF16toUTF8(aTitle
, name
);
317 mFilters
.AppendElement(filter
);
318 mFilterNames
.AppendElement(name
);
324 nsFilePicker::SetDefaultString(const nsAString
& aString
) {
331 nsFilePicker::GetDefaultString(nsAString
& aString
) {
333 return NS_ERROR_FAILURE
;
337 nsFilePicker::SetDefaultExtension(const nsAString
& aExtension
) {
338 mDefaultExtension
= aExtension
;
344 nsFilePicker::GetDefaultExtension(nsAString
& aExtension
) {
345 aExtension
= mDefaultExtension
;
351 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
352 *aFilterIndex
= mSelectedType
;
358 nsFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
359 mSelectedType
= aFilterIndex
;
365 nsFilePicker::GetFile(nsIFile
** aFile
) {
366 NS_ENSURE_ARG_POINTER(aFile
);
369 nsCOMPtr
<nsIURI
> uri
;
370 nsresult rv
= GetFileURL(getter_AddRefs(uri
));
373 nsCOMPtr
<nsIFileURL
> fileURL(do_QueryInterface(uri
, &rv
));
374 NS_ENSURE_SUCCESS(rv
, rv
);
376 nsCOMPtr
<nsIFile
> file
;
377 rv
= fileURL
->GetFile(getter_AddRefs(file
));
378 NS_ENSURE_SUCCESS(rv
, rv
);
385 nsFilePicker::GetFileURL(nsIURI
** aFileURL
) {
387 return NS_NewURI(aFileURL
, mFileURL
);
391 nsFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
392 NS_ENSURE_ARG_POINTER(aFiles
);
394 if (mMode
== nsIFilePicker::modeOpenMultiple
) {
395 return NS_NewArrayEnumerator(aFiles
, mFiles
, NS_GET_IID(nsIFile
));
398 return NS_ERROR_FAILURE
;
401 nsresult
nsFilePicker::Show(nsIFilePicker::ResultCode
* aReturn
) {
402 NS_ENSURE_ARG_POINTER(aReturn
);
404 nsresult rv
= Open(nullptr);
405 if (NS_FAILED(rv
)) return rv
;
407 while (mFileChooser
) {
408 g_main_context_iteration(nullptr, TRUE
);
416 nsFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
417 // Can't show two dialogs concurrently with the same filepicker
418 if (mFileChooser
) return NS_ERROR_NOT_AVAILABLE
;
420 if (MaybeBlockFilePicker(aCallback
)) {
424 NS_ConvertUTF16toUTF8
title(mTitle
);
426 GtkWindow
* parent_widget
=
427 GTK_WINDOW(mParentWidget
->GetNativeData(NS_NATIVE_SHELLWIDGET
));
429 GtkFileChooserAction action
= GetGtkFileChooserAction(mMode
);
431 const gchar
* accept_button
;
432 NS_ConvertUTF16toUTF8
buttonLabel(mOkButtonLabel
);
433 if (!mOkButtonLabel
.IsEmpty()) {
434 accept_button
= buttonLabel
.get();
436 accept_button
= nullptr;
440 GtkFileChooserNew(title
.get(), parent_widget
, action
, accept_button
);
442 // If we have --enable-proxy-bypass-protection, then don't allow
443 // remote URLs to be used.
444 #ifndef MOZ_PROXY_BYPASS_PROTECTION
446 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser
), FALSE
);
450 if (action
== GTK_FILE_CHOOSER_ACTION_OPEN
||
451 action
== GTK_FILE_CHOOSER_ACTION_SAVE
) {
452 GtkWidget
* img_preview
= gtk_image_new();
453 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser
),
455 g_signal_connect(file_chooser
, "update-preview",
456 G_CALLBACK(UpdateFilePreviewWidget
), img_preview
);
459 GtkFileChooserSetModal(file_chooser
, parent_widget
, TRUE
);
461 NS_ConvertUTF16toUTF8
defaultName(mDefault
);
463 case nsIFilePicker::modeOpenMultiple
:
464 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser
),
467 case nsIFilePicker::modeSave
:
468 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser
),
473 /* no additional setup needed */
477 nsCOMPtr
<nsIFile
> defaultPath
;
478 if (mDisplayDirectory
) {
479 mDisplayDirectory
->Clone(getter_AddRefs(defaultPath
));
480 } else if (mPrevDisplayDirectory
) {
481 mPrevDisplayDirectory
->Clone(getter_AddRefs(defaultPath
));
485 if (!defaultName
.IsEmpty() && mMode
!= nsIFilePicker::modeSave
) {
486 // Try to select the intended file. Even if it doesn't exist, GTK still
487 // switches directories.
488 defaultPath
->AppendNative(defaultName
);
490 defaultPath
->GetNativePath(path
);
491 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser
), path
.get());
493 nsAutoCString directory
;
494 defaultPath
->GetNativePath(directory
);
496 // Workaround for problematic refcounting in GTK3 before 3.16.
497 // We need to keep a reference to the dialog's internal delegate.
498 // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
499 // delegate by the time this gets processed in the event loop.
500 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
501 if (GTK_IS_DIALOG(file_chooser
)) {
502 GtkDialog
* dialog
= GTK_DIALOG(file_chooser
);
503 GtkContainer
* area
= GTK_CONTAINER(gtk_dialog_get_content_area(dialog
));
504 gtk_container_forall(
506 [](GtkWidget
* widget
, gpointer data
) {
507 if (GTK_IS_FILE_CHOOSER_WIDGET(widget
)) {
508 auto result
= static_cast<GtkFileChooserWidget
**>(data
);
509 *result
= GTK_FILE_CHOOSER_WIDGET(widget
);
512 &mFileChooserDelegate
);
514 if (mFileChooserDelegate
!= nullptr) {
515 g_object_ref(mFileChooserDelegate
);
518 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser
),
523 if (GTK_IS_DIALOG(file_chooser
)) {
524 gtk_dialog_set_default_response(GTK_DIALOG(file_chooser
),
525 GTK_RESPONSE_ACCEPT
);
528 int32_t count
= mFilters
.Length();
529 for (int32_t i
= 0; i
< count
; ++i
) {
530 // This is fun... the GTK file picker does not accept a list of filters
531 // so we need to split out each string, and add it manually.
533 char** patterns
= g_strsplit(mFilters
[i
].get(), ";", -1);
535 return NS_ERROR_OUT_OF_MEMORY
;
538 GtkFileFilter
* filter
= gtk_file_filter_new();
539 for (int j
= 0; patterns
[j
] != nullptr; ++j
) {
540 nsAutoCString caseInsensitiveFilter
=
541 MakeCaseInsensitiveShellGlob(g_strstrip(patterns
[j
]));
542 gtk_file_filter_add_pattern(filter
, caseInsensitiveFilter
.get());
545 g_strfreev(patterns
);
547 if (!mFilterNames
[i
].IsEmpty()) {
548 // If we have a name for our filter, let's use that.
549 const char* filter_name
= mFilterNames
[i
].get();
550 gtk_file_filter_set_name(filter
, filter_name
);
552 // If we don't have a name, let's just use the filter pattern.
553 const char* filter_pattern
= mFilters
[i
].get();
554 gtk_file_filter_set_name(filter
, filter_pattern
);
557 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser
), filter
);
559 // Set the initially selected filter
560 if (mSelectedType
== i
) {
561 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser
), filter
);
565 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser
),
568 mFileChooser
= file_chooser
;
569 mCallback
= aCallback
;
571 g_signal_connect(file_chooser
, "response", G_CALLBACK(OnResponse
), this);
572 GtkFileChooserShow(file_chooser
);
578 void nsFilePicker::OnResponse(void* file_chooser
, gint response_id
,
579 gpointer user_data
) {
580 static_cast<nsFilePicker
*>(user_data
)->Done(file_chooser
, response_id
);
584 void nsFilePicker::OnDestroy(GtkWidget
* file_chooser
, gpointer user_data
) {
585 static_cast<nsFilePicker
*>(user_data
)->Done(file_chooser
,
586 GTK_RESPONSE_CANCEL
);
589 bool nsFilePicker::WarnForNonReadableFile(void* file_chooser
) {
590 nsCOMPtr
<nsIFile
> file
;
591 GetFile(getter_AddRefs(file
));
596 bool isReadable
= false;
597 file
->IsReadable(&isReadable
);
602 nsCOMPtr
<nsIStringBundleService
> stringService
=
603 mozilla::components::StringBundle::Service();
604 if (!stringService
) {
608 nsCOMPtr
<nsIStringBundle
> filepickerBundle
;
609 nsresult rv
= stringService
->CreateBundle(
610 "chrome://global/locale/filepicker.properties",
611 getter_AddRefs(filepickerBundle
));
616 nsAutoString errorMessage
;
617 rv
= filepickerBundle
->GetStringFromName("selectedFileNotReadableError",
623 GtkDialogFlags flags
= GTK_DIALOG_DESTROY_WITH_PARENT
;
624 auto* cancel_dialog
= gtk_message_dialog_new(
625 GTK_WINDOW(file_chooser
), flags
, GTK_MESSAGE_ERROR
, GTK_BUTTONS_CLOSE
,
626 "%s", NS_ConvertUTF16toUTF8(errorMessage
).get());
627 gtk_dialog_run(GTK_DIALOG(cancel_dialog
));
628 gtk_widget_destroy(cancel_dialog
);
633 void nsFilePicker::Done(void* file_chooser
, gint response
) {
634 mFileChooser
= nullptr;
636 nsIFilePicker::ResultCode result
;
638 case GTK_RESPONSE_OK
:
639 case GTK_RESPONSE_ACCEPT
:
640 ReadValuesFromFileChooser(file_chooser
);
641 result
= nsIFilePicker::returnOK
;
642 if (mMode
== nsIFilePicker::modeSave
) {
643 nsCOMPtr
<nsIFile
> file
;
644 GetFile(getter_AddRefs(file
));
647 file
->Exists(&exists
);
648 if (exists
) result
= nsIFilePicker::returnReplace
;
650 } else if (mMode
== nsIFilePicker::modeOpen
) {
651 if (WarnForNonReadableFile(file_chooser
)) {
652 result
= nsIFilePicker::returnCancel
;
657 case GTK_RESPONSE_CANCEL
:
658 case GTK_RESPONSE_CLOSE
:
659 case GTK_RESPONSE_DELETE_EVENT
:
660 result
= nsIFilePicker::returnCancel
;
664 NS_WARNING("Unexpected response");
665 result
= nsIFilePicker::returnCancel
;
669 // A "response" signal won't be sent again but "destroy" will be.
670 g_signal_handlers_disconnect_by_func(file_chooser
, FuncToGpointer(OnDestroy
),
673 // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
674 // OnDestroy, the widget would be destroyed anyway but it is fine if
675 // gtk_widget_destroy is called more than once. gtk_widget_destroy has
676 // requests that any remaining references be released, but the reference
677 // count will not be decremented again if GtkWindow's reference has already
679 GtkFileChooserDestroy(file_chooser
);
681 if (mFileChooserDelegate
) {
682 // Properly deref our acquired reference. We call this after
683 // gtk_widget_destroy() to try and ensure that pending file info
684 // queries caused by updating the current folder have been cancelled.
685 // However, we do not know for certain when the callback will run after
688 [](gpointer data
) -> gboolean
{
689 g_object_unref(data
);
690 return G_SOURCE_REMOVE
;
692 mFileChooserDelegate
);
693 mFileChooserDelegate
= nullptr;
697 mCallback
->Done(result
);
705 // All below functions available as of GTK 3.20+
706 void* nsFilePicker::GtkFileChooserNew(const gchar
* title
, GtkWindow
* parent
,
707 GtkFileChooserAction action
,
708 const gchar
* accept_label
) {
709 static auto sGtkFileChooserNativeNewPtr
=
710 (void* (*)(const gchar
*, GtkWindow
*, GtkFileChooserAction
, const gchar
*,
711 const gchar
*))dlsym(RTLD_DEFAULT
,
712 "gtk_file_chooser_native_new");
713 if (mUseNativeFileChooser
&& sGtkFileChooserNativeNewPtr
!= nullptr) {
714 return (*sGtkFileChooserNativeNewPtr
)(title
, parent
, action
, accept_label
,
717 if (accept_label
== nullptr) {
718 accept_label
= (action
== GTK_FILE_CHOOSER_ACTION_SAVE
) ? GTK_STOCK_SAVE
721 GtkWidget
* file_chooser
= gtk_file_chooser_dialog_new(
722 title
, parent
, action
, GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
723 accept_label
, GTK_RESPONSE_ACCEPT
, nullptr);
724 gtk_dialog_set_alternative_button_order(
725 GTK_DIALOG(file_chooser
), GTK_RESPONSE_ACCEPT
, GTK_RESPONSE_CANCEL
, -1);
729 void nsFilePicker::GtkFileChooserShow(void* file_chooser
) {
730 static auto sGtkNativeDialogShowPtr
=
731 (void (*)(void*))dlsym(RTLD_DEFAULT
, "gtk_native_dialog_show");
732 if (mUseNativeFileChooser
&& sGtkNativeDialogShowPtr
!= nullptr) {
733 const char* portalEnvString
= g_getenv("GTK_USE_PORTAL");
735 (portalEnvString
&& *portalEnvString
== '0') || !portalEnvString
;
737 setenv("GTK_USE_PORTAL", "1", true);
739 (*sGtkNativeDialogShowPtr
)(file_chooser
);
741 unsetenv("GTK_USE_PORTAL");
744 g_signal_connect(file_chooser
, "destroy", G_CALLBACK(OnDestroy
), this);
745 gtk_widget_show(GTK_WIDGET(file_chooser
));
749 void nsFilePicker::GtkFileChooserDestroy(void* file_chooser
) {
750 static auto sGtkNativeDialogDestroyPtr
=
751 (void (*)(void*))dlsym(RTLD_DEFAULT
, "gtk_native_dialog_destroy");
752 if (mUseNativeFileChooser
&& sGtkNativeDialogDestroyPtr
!= nullptr) {
753 (*sGtkNativeDialogDestroyPtr
)(file_chooser
);
755 gtk_widget_destroy(GTK_WIDGET(file_chooser
));
759 void nsFilePicker::GtkFileChooserSetModal(void* file_chooser
,
760 GtkWindow
* parent_widget
,
762 static auto sGtkNativeDialogSetModalPtr
= (void (*)(void*, gboolean
))dlsym(
763 RTLD_DEFAULT
, "gtk_native_dialog_set_modal");
764 if (mUseNativeFileChooser
&& sGtkNativeDialogSetModalPtr
!= nullptr) {
765 (*sGtkNativeDialogSetModalPtr
)(file_chooser
, modal
);
767 GtkWindow
* window
= GTK_WINDOW(file_chooser
);
768 gtk_window_set_modal(window
, modal
);
769 if (parent_widget
!= nullptr) {
770 gtk_window_set_destroy_with_parent(window
, modal
);