Backed out 3 changesets (bug 1870106, bug 1845276) for causing doc generate failures...
[gecko.git] / widget / gtk / nsFilePicker.cpp
blob09e73b192780cab626b700b3cb46ee8cc3c1c7ac
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/. */
6 #include <dlfcn.h>
7 #include <gtk/gtk.h>
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
12 #include "mozilla/Types.h"
13 #include "AsyncDBus.h"
14 #include "nsGtkUtils.h"
15 #include "nsIFileURL.h"
16 #include "nsIGIOService.h"
17 #include "nsIURI.h"
18 #include "nsIWidget.h"
19 #include "nsIFile.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"
34 #undef LOG
35 #ifdef MOZ_LOGGING
36 # include "mozilla/Logging.h"
37 # include "nsTArray.h"
38 # include "Units.h"
39 extern mozilla::LazyLogModule gWidgetLog;
40 # define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
41 #else
42 # define LOG(args)
43 #endif /* MOZ_LOGGING */
45 using namespace mozilla;
46 using mozilla::dom::Promise;
48 #define MAX_PREVIEW_SIZE 180
49 // bug 1184009
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;
59 switch (aMode) {
60 case nsIFilePicker::modeSave:
61 action = GTK_FILE_CHOOSER_ACTION_SAVE;
62 break;
64 case nsIFilePicker::modeGetFolder:
65 action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
66 break;
68 case nsIFilePicker::modeOpen:
69 case nsIFilePicker::modeOpenMultiple:
70 action = GTK_FILE_CHOOSER_ACTION_OPEN;
71 break;
73 default:
74 NS_WARNING("Unknown nsIFilePicker mode");
75 action = GTK_FILE_CHOOSER_ACTION_OPEN;
76 break;
79 return action;
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);
86 struct stat st_buf;
88 if (!image_filename) {
89 gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
90 return;
93 gint preview_width = 0;
94 gint preview_height = 0;
95 /* check type of file
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);
112 return;
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);
120 } else {
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);
128 return;
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
137 // nice
138 gint x_padding =
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) {
148 // aPattern is UTF8
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]);
157 continue;
160 // add the lowercase and uppercase version of a character to a bracket
161 // match, so it matches either the lowercase or uppercase char.
162 result.Append('[');
163 result.Append(g_ascii_tolower(aPattern[i]));
164 result.Append(g_ascii_toupper(aPattern[i]));
165 result.Append(']');
168 return result;
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;
183 nsresult rv =
184 NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
185 false, getter_AddRefs(localfile));
186 if (NS_SUCCEEDED(rv)) {
187 nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array);
188 files.AppendObject(localfile);
191 g_free(filename);
194 void nsFilePicker::ReadValuesFromFileChooser(void* file_chooser) {
195 mFiles.Clear();
197 if (mMode == nsIFilePicker::modeOpenMultiple) {
198 mFileURL.Truncate();
200 GSList* list =
201 gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
202 g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
203 g_slist_free(list);
204 } else {
205 gchar* filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
206 mFileURL.Assign(filename);
207 g_free(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));
221 if (file) {
222 nsCOMPtr<nsIFile> dir;
223 file->GetParent(getter_AddRefs(dir));
224 if (dir) {
225 dir.swap(mPrevDisplayDirectory);
230 void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
231 mParentWidget = aParent;
232 mTitle.Assign(aTitle);
235 NS_IMETHODIMP
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";
249 MOZ_ASSERT(aCx);
250 MOZ_ASSERT(aRetPromise);
252 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
253 if (NS_WARN_IF(!globalObject)) {
254 return NS_ERROR_FAILURE;
257 ErrorResult result;
258 RefPtr<Promise> retPromise = Promise::Create(globalObject, result);
259 if (NS_WARN_IF(result.Failed())) {
260 return result.StealNSResult();
263 widget::CreateDBusProxyForBus(
264 G_BUS_TYPE_SESSION,
265 GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
266 /* aInterfaceInfo = */ nullptr, kFreedesktopPortalName,
267 kFreedesktopPortalPath, kFreedesktopPortalFileChooser)
268 ->Then(
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));
280 if (property) {
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);
294 return NS_OK;
295 #else
296 return nsBaseFilePicker::IsModeSupported(aMode, aCx, aRetPromise);
297 #endif
300 NS_IMETHODIMP
301 nsFilePicker::AppendFilters(int32_t aFilterMask) {
302 mAllowURLs = !!(aFilterMask & filterAllowURLs);
303 return nsBaseFilePicker::AppendFilters(aFilterMask);
306 NS_IMETHODIMP
307 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
308 if (aFilter.EqualsLiteral("..apps")) {
309 // No platform specific thing we can do here, really....
310 return NS_OK;
313 nsAutoCString filter, name;
314 CopyUTF16toUTF8(aFilter, filter);
315 CopyUTF16toUTF8(aTitle, name);
317 mFilters.AppendElement(filter);
318 mFilterNames.AppendElement(name);
320 return NS_OK;
323 NS_IMETHODIMP
324 nsFilePicker::SetDefaultString(const nsAString& aString) {
325 mDefault = aString;
327 return NS_OK;
330 NS_IMETHODIMP
331 nsFilePicker::GetDefaultString(nsAString& aString) {
332 // Per API...
333 return NS_ERROR_FAILURE;
336 NS_IMETHODIMP
337 nsFilePicker::SetDefaultExtension(const nsAString& aExtension) {
338 mDefaultExtension = aExtension;
340 return NS_OK;
343 NS_IMETHODIMP
344 nsFilePicker::GetDefaultExtension(nsAString& aExtension) {
345 aExtension = mDefaultExtension;
347 return NS_OK;
350 NS_IMETHODIMP
351 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
352 *aFilterIndex = mSelectedType;
354 return NS_OK;
357 NS_IMETHODIMP
358 nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
359 mSelectedType = aFilterIndex;
361 return NS_OK;
364 NS_IMETHODIMP
365 nsFilePicker::GetFile(nsIFile** aFile) {
366 NS_ENSURE_ARG_POINTER(aFile);
368 *aFile = nullptr;
369 nsCOMPtr<nsIURI> uri;
370 nsresult rv = GetFileURL(getter_AddRefs(uri));
371 if (!uri) return rv;
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);
380 file.forget(aFile);
381 return NS_OK;
384 NS_IMETHODIMP
385 nsFilePicker::GetFileURL(nsIURI** aFileURL) {
386 *aFileURL = nullptr;
387 return NS_NewURI(aFileURL, mFileURL);
390 NS_IMETHODIMP
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);
411 *aReturn = mResult;
412 return NS_OK;
415 NS_IMETHODIMP
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 NS_ConvertUTF16toUTF8 title(mTitle);
422 GtkWindow* parent_widget =
423 GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
425 GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
427 const gchar* accept_button;
428 NS_ConvertUTF16toUTF8 buttonLabel(mOkButtonLabel);
429 if (!mOkButtonLabel.IsEmpty()) {
430 accept_button = buttonLabel.get();
431 } else {
432 accept_button = nullptr;
435 void* file_chooser =
436 GtkFileChooserNew(title.get(), parent_widget, action, accept_button);
438 // If we have --enable-proxy-bypass-protection, then don't allow
439 // remote URLs to be used.
440 #ifndef MOZ_PROXY_BYPASS_PROTECTION
441 if (mAllowURLs) {
442 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
444 #endif
446 if (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
447 action == GTK_FILE_CHOOSER_ACTION_SAVE) {
448 GtkWidget* img_preview = gtk_image_new();
449 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser),
450 img_preview);
451 g_signal_connect(file_chooser, "update-preview",
452 G_CALLBACK(UpdateFilePreviewWidget), img_preview);
455 GtkFileChooserSetModal(file_chooser, parent_widget, TRUE);
457 NS_ConvertUTF16toUTF8 defaultName(mDefault);
458 switch (mMode) {
459 case nsIFilePicker::modeOpenMultiple:
460 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser),
461 TRUE);
462 break;
463 case nsIFilePicker::modeSave:
464 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
465 defaultName.get());
466 break;
468 default:
469 /* no additional setup needed */
470 break;
473 nsCOMPtr<nsIFile> defaultPath;
474 if (mDisplayDirectory) {
475 mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
476 } else if (mPrevDisplayDirectory) {
477 mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
480 if (defaultPath) {
481 if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
482 // Try to select the intended file. Even if it doesn't exist, GTK still
483 // switches directories.
484 defaultPath->AppendNative(defaultName);
485 nsAutoCString path;
486 defaultPath->GetNativePath(path);
487 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
488 } else {
489 nsAutoCString directory;
490 defaultPath->GetNativePath(directory);
492 // Workaround for problematic refcounting in GTK3 before 3.16.
493 // We need to keep a reference to the dialog's internal delegate.
494 // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
495 // delegate by the time this gets processed in the event loop.
496 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
497 if (GTK_IS_DIALOG(file_chooser)) {
498 GtkDialog* dialog = GTK_DIALOG(file_chooser);
499 GtkContainer* area = GTK_CONTAINER(gtk_dialog_get_content_area(dialog));
500 gtk_container_forall(
501 area,
502 [](GtkWidget* widget, gpointer data) {
503 if (GTK_IS_FILE_CHOOSER_WIDGET(widget)) {
504 auto result = static_cast<GtkFileChooserWidget**>(data);
505 *result = GTK_FILE_CHOOSER_WIDGET(widget);
508 &mFileChooserDelegate);
510 if (mFileChooserDelegate != nullptr) {
511 g_object_ref(mFileChooserDelegate);
514 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
515 directory.get());
519 if (GTK_IS_DIALOG(file_chooser)) {
520 gtk_dialog_set_default_response(GTK_DIALOG(file_chooser),
521 GTK_RESPONSE_ACCEPT);
524 int32_t count = mFilters.Length();
525 for (int32_t i = 0; i < count; ++i) {
526 // This is fun... the GTK file picker does not accept a list of filters
527 // so we need to split out each string, and add it manually.
529 char** patterns = g_strsplit(mFilters[i].get(), ";", -1);
530 if (!patterns) {
531 return NS_ERROR_OUT_OF_MEMORY;
534 GtkFileFilter* filter = gtk_file_filter_new();
535 for (int j = 0; patterns[j] != nullptr; ++j) {
536 nsAutoCString caseInsensitiveFilter =
537 MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
538 gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
541 g_strfreev(patterns);
543 if (!mFilterNames[i].IsEmpty()) {
544 // If we have a name for our filter, let's use that.
545 const char* filter_name = mFilterNames[i].get();
546 gtk_file_filter_set_name(filter, filter_name);
547 } else {
548 // If we don't have a name, let's just use the filter pattern.
549 const char* filter_pattern = mFilters[i].get();
550 gtk_file_filter_set_name(filter, filter_pattern);
553 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
555 // Set the initially selected filter
556 if (mSelectedType == i) {
557 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
561 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser),
562 TRUE);
564 mFileChooser = file_chooser;
565 mCallback = aCallback;
566 NS_ADDREF_THIS();
567 g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
568 GtkFileChooserShow(file_chooser);
570 return NS_OK;
573 NS_IMETHODIMP
574 nsFilePicker::Close() {
575 if (mFileChooser) {
576 // Call ourself as done.
577 Done(mFileChooser, GTK_RESPONSE_CLOSE);
580 return NS_OK;
583 /* static */
584 void nsFilePicker::OnResponse(void* file_chooser, gint response_id,
585 gpointer user_data) {
586 static_cast<nsFilePicker*>(user_data)->Done(file_chooser, response_id);
589 /* static */
590 void nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data) {
591 static_cast<nsFilePicker*>(user_data)->Done(file_chooser,
592 GTK_RESPONSE_CANCEL);
595 bool nsFilePicker::WarnForNonReadableFile(void* file_chooser) {
596 nsCOMPtr<nsIFile> file;
597 GetFile(getter_AddRefs(file));
598 if (!file) {
599 return false;
602 bool isReadable = false;
603 file->IsReadable(&isReadable);
604 if (isReadable) {
605 return false;
608 nsCOMPtr<nsIStringBundleService> stringService =
609 mozilla::components::StringBundle::Service();
610 if (!stringService) {
611 return false;
614 nsCOMPtr<nsIStringBundle> filepickerBundle;
615 nsresult rv = stringService->CreateBundle(
616 "chrome://global/locale/filepicker.properties",
617 getter_AddRefs(filepickerBundle));
618 if (NS_FAILED(rv)) {
619 return false;
622 nsAutoString errorMessage;
623 rv = filepickerBundle->GetStringFromName("selectedFileNotReadableError",
624 errorMessage);
625 if (NS_FAILED(rv)) {
626 return false;
629 GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
630 auto* cancel_dialog = gtk_message_dialog_new(
631 GTK_WINDOW(file_chooser), flags, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
632 "%s", NS_ConvertUTF16toUTF8(errorMessage).get());
633 gtk_dialog_run(GTK_DIALOG(cancel_dialog));
634 gtk_widget_destroy(cancel_dialog);
636 return true;
639 void nsFilePicker::Done(void* file_chooser, gint response) {
640 mFileChooser = nullptr;
642 nsIFilePicker::ResultCode result;
643 switch (response) {
644 case GTK_RESPONSE_OK:
645 case GTK_RESPONSE_ACCEPT:
646 ReadValuesFromFileChooser(file_chooser);
647 result = nsIFilePicker::returnOK;
648 if (mMode == nsIFilePicker::modeSave) {
649 nsCOMPtr<nsIFile> file;
650 GetFile(getter_AddRefs(file));
651 if (file) {
652 bool exists = false;
653 file->Exists(&exists);
654 if (exists) result = nsIFilePicker::returnReplace;
656 } else if (mMode == nsIFilePicker::modeOpen) {
657 if (WarnForNonReadableFile(file_chooser)) {
658 result = nsIFilePicker::returnCancel;
661 break;
663 case GTK_RESPONSE_CANCEL:
664 case GTK_RESPONSE_CLOSE:
665 case GTK_RESPONSE_DELETE_EVENT:
666 result = nsIFilePicker::returnCancel;
667 break;
669 default:
670 NS_WARNING("Unexpected response");
671 result = nsIFilePicker::returnCancel;
672 break;
675 // A "response" signal won't be sent again but "destroy" will be.
676 g_signal_handlers_disconnect_by_func(file_chooser, FuncToGpointer(OnDestroy),
677 this);
679 // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
680 // OnDestroy, the widget would be destroyed anyway but it is fine if
681 // gtk_widget_destroy is called more than once. gtk_widget_destroy has
682 // requests that any remaining references be released, but the reference
683 // count will not be decremented again if GtkWindow's reference has already
684 // been released.
685 GtkFileChooserDestroy(file_chooser);
687 if (mFileChooserDelegate) {
688 // Properly deref our acquired reference. We call this after
689 // gtk_widget_destroy() to try and ensure that pending file info
690 // queries caused by updating the current folder have been cancelled.
691 // However, we do not know for certain when the callback will run after
692 // cancelled.
693 g_idle_add(
694 [](gpointer data) -> gboolean {
695 g_object_unref(data);
696 return G_SOURCE_REMOVE;
698 mFileChooserDelegate);
699 mFileChooserDelegate = nullptr;
702 if (mCallback) {
703 mCallback->Done(result);
704 mCallback = nullptr;
705 } else {
706 mResult = result;
708 NS_RELEASE_THIS();
711 // All below functions available as of GTK 3.20+
712 void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent,
713 GtkFileChooserAction action,
714 const gchar* accept_label) {
715 static auto sGtkFileChooserNativeNewPtr =
716 (void* (*)(const gchar*, GtkWindow*, GtkFileChooserAction, const gchar*,
717 const gchar*))dlsym(RTLD_DEFAULT,
718 "gtk_file_chooser_native_new");
719 if (mUseNativeFileChooser && sGtkFileChooserNativeNewPtr != nullptr) {
720 return (*sGtkFileChooserNativeNewPtr)(title, parent, action, accept_label,
721 nullptr);
723 if (accept_label == nullptr) {
724 accept_label = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ? GTK_STOCK_SAVE
725 : GTK_STOCK_OPEN;
727 GtkWidget* file_chooser = gtk_file_chooser_dialog_new(
728 title, parent, action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
729 accept_label, GTK_RESPONSE_ACCEPT, nullptr);
730 gtk_dialog_set_alternative_button_order(
731 GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1);
732 return file_chooser;
735 void nsFilePicker::GtkFileChooserShow(void* file_chooser) {
736 static auto sGtkNativeDialogShowPtr =
737 (void (*)(void*))dlsym(RTLD_DEFAULT, "gtk_native_dialog_show");
738 if (mUseNativeFileChooser && sGtkNativeDialogShowPtr != nullptr) {
739 const char* portalEnvString = g_getenv("GTK_USE_PORTAL");
740 bool setPortalEnv =
741 (portalEnvString && *portalEnvString == '0') || !portalEnvString;
742 if (setPortalEnv) {
743 setenv("GTK_USE_PORTAL", "1", true);
745 (*sGtkNativeDialogShowPtr)(file_chooser);
746 if (setPortalEnv) {
747 unsetenv("GTK_USE_PORTAL");
749 } else {
750 g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
751 gtk_widget_show(GTK_WIDGET(file_chooser));
755 void nsFilePicker::GtkFileChooserDestroy(void* file_chooser) {
756 static auto sGtkNativeDialogDestroyPtr =
757 (void (*)(void*))dlsym(RTLD_DEFAULT, "gtk_native_dialog_destroy");
758 if (mUseNativeFileChooser && sGtkNativeDialogDestroyPtr != nullptr) {
759 (*sGtkNativeDialogDestroyPtr)(file_chooser);
760 } else {
761 gtk_widget_destroy(GTK_WIDGET(file_chooser));
765 void nsFilePicker::GtkFileChooserSetModal(void* file_chooser,
766 GtkWindow* parent_widget,
767 gboolean modal) {
768 static auto sGtkNativeDialogSetModalPtr = (void (*)(void*, gboolean))dlsym(
769 RTLD_DEFAULT, "gtk_native_dialog_set_modal");
770 if (mUseNativeFileChooser && sGtkNativeDialogSetModalPtr != nullptr) {
771 (*sGtkNativeDialogSetModalPtr)(file_chooser, modal);
772 } else {
773 GtkWindow* window = GTK_WINDOW(file_chooser);
774 gtk_window_set_modal(window, modal);
775 if (parent_widget != nullptr) {
776 gtk_window_set_destroy_with_parent(window, modal);
781 #undef LOG