Line data Source code
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/. */
5 :
6 : #include "mozilla/Types.h"
7 : #include <sys/types.h>
8 : #include <sys/stat.h>
9 : #include <unistd.h>
10 :
11 : #include <gtk/gtk.h>
12 :
13 : #include "nsGtkUtils.h"
14 : #include "nsIFileURL.h"
15 : #include "nsIURI.h"
16 : #include "nsIWidget.h"
17 : #include "nsIFile.h"
18 : #include "nsIStringBundle.h"
19 :
20 : #include "nsArrayEnumerator.h"
21 : #include "nsMemory.h"
22 : #include "nsEnumeratorUtils.h"
23 : #include "nsNetUtil.h"
24 : #include "nsReadableUtils.h"
25 : #include "mozcontainer.h"
26 :
27 : #include "nsFilePicker.h"
28 :
29 : using namespace mozilla;
30 :
31 : #define MAX_PREVIEW_SIZE 180
32 : // bug 1184009
33 : #define MAX_PREVIEW_SOURCE_SIZE 4096
34 :
35 : nsIFile *nsFilePicker::mPrevDisplayDirectory = nullptr;
36 :
37 : void
38 0 : nsFilePicker::Shutdown()
39 : {
40 0 : NS_IF_RELEASE(mPrevDisplayDirectory);
41 0 : }
42 :
43 : static GtkFileChooserAction
44 0 : GetGtkFileChooserAction(int16_t aMode)
45 : {
46 : GtkFileChooserAction action;
47 :
48 0 : switch (aMode) {
49 : case nsIFilePicker::modeSave:
50 0 : action = GTK_FILE_CHOOSER_ACTION_SAVE;
51 0 : break;
52 :
53 : case nsIFilePicker::modeGetFolder:
54 0 : action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
55 0 : break;
56 :
57 : case nsIFilePicker::modeOpen:
58 : case nsIFilePicker::modeOpenMultiple:
59 0 : action = GTK_FILE_CHOOSER_ACTION_OPEN;
60 0 : break;
61 :
62 : default:
63 0 : NS_WARNING("Unknown nsIFilePicker mode");
64 0 : action = GTK_FILE_CHOOSER_ACTION_OPEN;
65 0 : break;
66 : }
67 :
68 0 : return action;
69 : }
70 :
71 :
72 : static void
73 0 : UpdateFilePreviewWidget(GtkFileChooser *file_chooser,
74 : gpointer preview_widget_voidptr)
75 : {
76 0 : GtkImage *preview_widget = GTK_IMAGE(preview_widget_voidptr);
77 0 : char *image_filename = gtk_file_chooser_get_preview_filename(file_chooser);
78 : struct stat st_buf;
79 :
80 0 : if (!image_filename) {
81 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
82 0 : return;
83 : }
84 :
85 0 : gint preview_width = 0;
86 0 : gint preview_height = 0;
87 : /* check type of file
88 : * if file is named pipe, Open is blocking which may lead to UI
89 : * nonresponsiveness; if file is directory/socket, it also isn't
90 : * likely to get preview */
91 0 : if (stat(image_filename, &st_buf) || (!S_ISREG(st_buf.st_mode))) {
92 0 : g_free(image_filename);
93 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
94 0 : return; /* stat failed or file is not regular */
95 : }
96 :
97 : GdkPixbufFormat *preview_format = gdk_pixbuf_get_file_info(image_filename,
98 : &preview_width,
99 0 : &preview_height);
100 0 : if (!preview_format ||
101 0 : preview_width <= 0 || preview_height <= 0 ||
102 0 : preview_width > MAX_PREVIEW_SOURCE_SIZE ||
103 0 : preview_height > MAX_PREVIEW_SOURCE_SIZE) {
104 0 : g_free(image_filename);
105 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
106 0 : return;
107 : }
108 :
109 0 : GdkPixbuf *preview_pixbuf = nullptr;
110 : // Only scale down images that are too big
111 0 : if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) {
112 : preview_pixbuf = gdk_pixbuf_new_from_file_at_size(image_filename,
113 : MAX_PREVIEW_SIZE,
114 : MAX_PREVIEW_SIZE,
115 0 : nullptr);
116 : }
117 : else {
118 0 : preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, nullptr);
119 : }
120 :
121 0 : g_free(image_filename);
122 :
123 0 : if (!preview_pixbuf) {
124 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
125 0 : return;
126 : }
127 :
128 0 : GdkPixbuf *preview_pixbuf_temp = preview_pixbuf;
129 0 : preview_pixbuf = gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp);
130 0 : g_object_unref(preview_pixbuf_temp);
131 :
132 : // This is the easiest way to do center alignment without worrying about containers
133 : // Minimum 3px padding each side (hence the 6) just to make things nice
134 0 : gint x_padding = (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2;
135 0 : gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0);
136 :
137 0 : gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf);
138 0 : g_object_unref(preview_pixbuf);
139 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
140 : }
141 :
142 : static nsAutoCString
143 0 : MakeCaseInsensitiveShellGlob(const char* aPattern) {
144 : // aPattern is UTF8
145 0 : nsAutoCString result;
146 0 : unsigned int len = strlen(aPattern);
147 :
148 0 : for (unsigned int i = 0; i < len; i++) {
149 0 : if (!g_ascii_isalpha(aPattern[i])) {
150 : // non-ASCII characters will also trigger this path, so unicode
151 : // is safely handled albeit case-sensitively
152 0 : result.Append(aPattern[i]);
153 0 : continue;
154 : }
155 :
156 : // add the lowercase and uppercase version of a character to a bracket
157 : // match, so it matches either the lowercase or uppercase char.
158 0 : result.Append('[');
159 0 : result.Append(g_ascii_tolower(aPattern[i]));
160 0 : result.Append(g_ascii_toupper(aPattern[i]));
161 0 : result.Append(']');
162 :
163 : }
164 :
165 0 : return result;
166 : }
167 :
168 0 : NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
169 :
170 0 : nsFilePicker::nsFilePicker()
171 : : mSelectedType(0)
172 : , mRunning(false)
173 : , mAllowURLs(false)
174 : #if (MOZ_WIDGET_GTK == 3)
175 0 : , mFileChooserDelegate(nullptr)
176 : #endif
177 : {
178 0 : }
179 :
180 0 : nsFilePicker::~nsFilePicker()
181 : {
182 0 : }
183 :
184 : void
185 0 : ReadMultipleFiles(gpointer filename, gpointer array)
186 : {
187 0 : nsCOMPtr<nsIFile> localfile;
188 0 : nsresult rv = NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
189 : false,
190 0 : getter_AddRefs(localfile));
191 0 : if (NS_SUCCEEDED(rv)) {
192 0 : nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array);
193 0 : files.AppendObject(localfile);
194 : }
195 :
196 0 : g_free(filename);
197 0 : }
198 :
199 : void
200 0 : nsFilePicker::ReadValuesFromFileChooser(GtkWidget *file_chooser)
201 : {
202 0 : mFiles.Clear();
203 :
204 0 : if (mMode == nsIFilePicker::modeOpenMultiple) {
205 0 : mFileURL.Truncate();
206 :
207 0 : GSList *list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
208 0 : g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
209 0 : g_slist_free(list);
210 : } else {
211 0 : gchar *filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
212 0 : mFileURL.Assign(filename);
213 0 : g_free(filename);
214 : }
215 :
216 0 : GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser));
217 0 : GSList *filter_list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser));
218 :
219 0 : mSelectedType = static_cast<int16_t>(g_slist_index(filter_list, filter));
220 0 : g_slist_free(filter_list);
221 :
222 : // Remember last used directory.
223 0 : nsCOMPtr<nsIFile> file;
224 0 : GetFile(getter_AddRefs(file));
225 0 : if (file) {
226 0 : nsCOMPtr<nsIFile> dir;
227 0 : file->GetParent(getter_AddRefs(dir));
228 0 : if (dir) {
229 0 : dir.swap(mPrevDisplayDirectory);
230 : }
231 : }
232 0 : }
233 :
234 : void
235 0 : nsFilePicker::InitNative(nsIWidget *aParent,
236 : const nsAString& aTitle)
237 : {
238 0 : mParentWidget = aParent;
239 0 : mTitle.Assign(aTitle);
240 0 : }
241 :
242 : NS_IMETHODIMP
243 0 : nsFilePicker::AppendFilters(int32_t aFilterMask)
244 : {
245 0 : mAllowURLs = !!(aFilterMask & filterAllowURLs);
246 0 : return nsBaseFilePicker::AppendFilters(aFilterMask);
247 : }
248 :
249 : NS_IMETHODIMP
250 0 : nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
251 : {
252 0 : if (aFilter.EqualsLiteral("..apps")) {
253 : // No platform specific thing we can do here, really....
254 0 : return NS_OK;
255 : }
256 :
257 0 : nsAutoCString filter, name;
258 0 : CopyUTF16toUTF8(aFilter, filter);
259 0 : CopyUTF16toUTF8(aTitle, name);
260 :
261 0 : mFilters.AppendElement(filter);
262 0 : mFilterNames.AppendElement(name);
263 :
264 0 : return NS_OK;
265 : }
266 :
267 : NS_IMETHODIMP
268 0 : nsFilePicker::SetDefaultString(const nsAString& aString)
269 : {
270 0 : mDefault = aString;
271 :
272 0 : return NS_OK;
273 : }
274 :
275 : NS_IMETHODIMP
276 0 : nsFilePicker::GetDefaultString(nsAString& aString)
277 : {
278 : // Per API...
279 0 : return NS_ERROR_FAILURE;
280 : }
281 :
282 : NS_IMETHODIMP
283 0 : nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
284 : {
285 0 : mDefaultExtension = aExtension;
286 :
287 0 : return NS_OK;
288 : }
289 :
290 : NS_IMETHODIMP
291 0 : nsFilePicker::GetDefaultExtension(nsAString& aExtension)
292 : {
293 0 : aExtension = mDefaultExtension;
294 :
295 0 : return NS_OK;
296 : }
297 :
298 : NS_IMETHODIMP
299 0 : nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
300 : {
301 0 : *aFilterIndex = mSelectedType;
302 :
303 0 : return NS_OK;
304 : }
305 :
306 : NS_IMETHODIMP
307 0 : nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
308 : {
309 0 : mSelectedType = aFilterIndex;
310 :
311 0 : return NS_OK;
312 : }
313 :
314 : NS_IMETHODIMP
315 0 : nsFilePicker::GetFile(nsIFile **aFile)
316 : {
317 0 : NS_ENSURE_ARG_POINTER(aFile);
318 :
319 0 : *aFile = nullptr;
320 0 : nsCOMPtr<nsIURI> uri;
321 0 : nsresult rv = GetFileURL(getter_AddRefs(uri));
322 0 : if (!uri)
323 0 : return rv;
324 :
325 0 : nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv));
326 0 : NS_ENSURE_SUCCESS(rv, rv);
327 :
328 0 : nsCOMPtr<nsIFile> file;
329 0 : rv = fileURL->GetFile(getter_AddRefs(file));
330 0 : NS_ENSURE_SUCCESS(rv, rv);
331 :
332 0 : file.forget(aFile);
333 0 : return NS_OK;
334 : }
335 :
336 : NS_IMETHODIMP
337 0 : nsFilePicker::GetFileURL(nsIURI **aFileURL)
338 : {
339 0 : *aFileURL = nullptr;
340 0 : return NS_NewURI(aFileURL, mFileURL);
341 : }
342 :
343 : NS_IMETHODIMP
344 0 : nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
345 : {
346 0 : NS_ENSURE_ARG_POINTER(aFiles);
347 :
348 0 : if (mMode == nsIFilePicker::modeOpenMultiple) {
349 0 : return NS_NewArrayEnumerator(aFiles, mFiles);
350 : }
351 :
352 0 : return NS_ERROR_FAILURE;
353 : }
354 :
355 : NS_IMETHODIMP
356 0 : nsFilePicker::Show(int16_t *aReturn)
357 : {
358 0 : NS_ENSURE_ARG_POINTER(aReturn);
359 :
360 0 : nsresult rv = Open(nullptr);
361 0 : if (NS_FAILED(rv))
362 0 : return rv;
363 :
364 0 : while (mRunning) {
365 0 : g_main_context_iteration(nullptr, TRUE);
366 : }
367 :
368 0 : *aReturn = mResult;
369 0 : return NS_OK;
370 : }
371 :
372 : NS_IMETHODIMP
373 0 : nsFilePicker::Open(nsIFilePickerShownCallback *aCallback)
374 : {
375 : // Can't show two dialogs concurrently with the same filepicker
376 0 : if (mRunning)
377 0 : return NS_ERROR_NOT_AVAILABLE;
378 :
379 0 : nsXPIDLCString title;
380 0 : title.Adopt(ToNewUTF8String(mTitle));
381 :
382 : GtkWindow *parent_widget =
383 0 : GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
384 :
385 0 : GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
386 :
387 : const gchar* accept_button;
388 0 : NS_ConvertUTF16toUTF8 buttonLabel(mOkButtonLabel);
389 0 : if (!mOkButtonLabel.IsEmpty()) {
390 0 : accept_button = buttonLabel.get();
391 : } else {
392 0 : accept_button = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ?
393 0 : GTK_STOCK_SAVE : GTK_STOCK_OPEN;
394 : }
395 :
396 : GtkWidget *file_chooser =
397 0 : gtk_file_chooser_dialog_new(title, parent_widget, action,
398 : GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
399 : accept_button, GTK_RESPONSE_ACCEPT,
400 0 : nullptr);
401 0 : gtk_dialog_set_alternative_button_order(GTK_DIALOG(file_chooser),
402 : GTK_RESPONSE_ACCEPT,
403 : GTK_RESPONSE_CANCEL,
404 0 : -1);
405 0 : if (mAllowURLs) {
406 0 : gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
407 : }
408 :
409 0 : if (action == GTK_FILE_CHOOSER_ACTION_OPEN || action == GTK_FILE_CHOOSER_ACTION_SAVE) {
410 0 : GtkWidget *img_preview = gtk_image_new();
411 0 : gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser), img_preview);
412 0 : g_signal_connect(file_chooser, "update-preview", G_CALLBACK(UpdateFilePreviewWidget), img_preview);
413 : }
414 :
415 0 : GtkWindow *window = GTK_WINDOW(file_chooser);
416 0 : gtk_window_set_modal(window, TRUE);
417 0 : if (parent_widget) {
418 0 : gtk_window_set_destroy_with_parent(window, TRUE);
419 : }
420 :
421 0 : NS_ConvertUTF16toUTF8 defaultName(mDefault);
422 0 : switch (mMode) {
423 : case nsIFilePicker::modeOpenMultiple:
424 0 : gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser), TRUE);
425 0 : break;
426 : case nsIFilePicker::modeSave:
427 0 : gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
428 0 : defaultName.get());
429 0 : break;
430 : }
431 :
432 0 : nsCOMPtr<nsIFile> defaultPath;
433 0 : if (mDisplayDirectory) {
434 0 : mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
435 0 : } else if (mPrevDisplayDirectory) {
436 0 : mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
437 : }
438 :
439 0 : if (defaultPath) {
440 0 : if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
441 : // Try to select the intended file. Even if it doesn't exist, GTK still switches
442 : // directories.
443 0 : defaultPath->AppendNative(defaultName);
444 0 : nsAutoCString path;
445 0 : defaultPath->GetNativePath(path);
446 0 : gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
447 : } else {
448 0 : nsAutoCString directory;
449 0 : defaultPath->GetNativePath(directory);
450 :
451 : #if (MOZ_WIDGET_GTK == 3)
452 : // Workaround for problematic refcounting in GTK3 before 3.16.
453 : // We need to keep a reference to the dialog's internal delegate.
454 : // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
455 : // delegate by the time this gets processed in the event loop.
456 : // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
457 0 : GtkDialog *dialog = GTK_DIALOG(file_chooser);
458 0 : GtkContainer *area = GTK_CONTAINER(gtk_dialog_get_content_area(dialog));
459 0 : gtk_container_forall(area, [](GtkWidget *widget,
460 0 : gpointer data) {
461 0 : if (GTK_IS_FILE_CHOOSER_WIDGET(widget)) {
462 0 : auto result = static_cast<GtkFileChooserWidget**>(data);
463 0 : *result = GTK_FILE_CHOOSER_WIDGET(widget);
464 : }
465 0 : }, &mFileChooserDelegate);
466 :
467 0 : if (mFileChooserDelegate)
468 0 : g_object_ref(mFileChooserDelegate);
469 : #endif
470 :
471 0 : gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
472 0 : directory.get());
473 : }
474 : }
475 :
476 0 : gtk_dialog_set_default_response(GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT);
477 :
478 0 : int32_t count = mFilters.Length();
479 0 : for (int32_t i = 0; i < count; ++i) {
480 : // This is fun... the GTK file picker does not accept a list of filters
481 : // so we need to split out each string, and add it manually.
482 :
483 0 : char **patterns = g_strsplit(mFilters[i].get(), ";", -1);
484 0 : if (!patterns) {
485 0 : return NS_ERROR_OUT_OF_MEMORY;
486 : }
487 :
488 0 : GtkFileFilter *filter = gtk_file_filter_new();
489 0 : for (int j = 0; patterns[j] != nullptr; ++j) {
490 0 : nsAutoCString caseInsensitiveFilter = MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
491 0 : gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
492 : }
493 :
494 0 : g_strfreev(patterns);
495 :
496 0 : if (!mFilterNames[i].IsEmpty()) {
497 : // If we have a name for our filter, let's use that.
498 0 : const char *filter_name = mFilterNames[i].get();
499 0 : gtk_file_filter_set_name(filter, filter_name);
500 : } else {
501 : // If we don't have a name, let's just use the filter pattern.
502 0 : const char *filter_pattern = mFilters[i].get();
503 0 : gtk_file_filter_set_name(filter, filter_pattern);
504 : }
505 :
506 0 : gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
507 :
508 : // Set the initially selected filter
509 0 : if (mSelectedType == i) {
510 0 : gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
511 : }
512 : }
513 :
514 0 : gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser), TRUE);
515 :
516 0 : mRunning = true;
517 0 : mCallback = aCallback;
518 0 : NS_ADDREF_THIS();
519 0 : g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
520 0 : g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
521 0 : gtk_widget_show(file_chooser);
522 :
523 0 : return NS_OK;
524 : }
525 :
526 : /* static */ void
527 0 : nsFilePicker::OnResponse(GtkWidget* file_chooser, gint response_id,
528 : gpointer user_data)
529 : {
530 : static_cast<nsFilePicker*>(user_data)->
531 0 : Done(file_chooser, response_id);
532 0 : }
533 :
534 : /* static */ void
535 0 : nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data)
536 : {
537 : static_cast<nsFilePicker*>(user_data)->
538 0 : Done(file_chooser, GTK_RESPONSE_CANCEL);
539 0 : }
540 :
541 : void
542 0 : nsFilePicker::Done(GtkWidget* file_chooser, gint response)
543 : {
544 0 : mRunning = false;
545 :
546 : int16_t result;
547 0 : switch (response) {
548 : case GTK_RESPONSE_OK:
549 : case GTK_RESPONSE_ACCEPT:
550 0 : ReadValuesFromFileChooser(file_chooser);
551 0 : result = nsIFilePicker::returnOK;
552 0 : if (mMode == nsIFilePicker::modeSave) {
553 0 : nsCOMPtr<nsIFile> file;
554 0 : GetFile(getter_AddRefs(file));
555 0 : if (file) {
556 0 : bool exists = false;
557 0 : file->Exists(&exists);
558 0 : if (exists)
559 0 : result = nsIFilePicker::returnReplace;
560 : }
561 : }
562 0 : break;
563 :
564 : case GTK_RESPONSE_CANCEL:
565 : case GTK_RESPONSE_CLOSE:
566 : case GTK_RESPONSE_DELETE_EVENT:
567 0 : result = nsIFilePicker::returnCancel;
568 0 : break;
569 :
570 : default:
571 0 : NS_WARNING("Unexpected response");
572 0 : result = nsIFilePicker::returnCancel;
573 0 : break;
574 : }
575 :
576 : // A "response" signal won't be sent again but "destroy" will be.
577 0 : g_signal_handlers_disconnect_by_func(file_chooser,
578 0 : FuncToGpointer(OnDestroy), this);
579 :
580 : // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
581 : // OnDestroy, the widget would be destroyed anyway but it is fine if
582 : // gtk_widget_destroy is called more than once. gtk_widget_destroy has
583 : // requests that any remaining references be released, but the reference
584 : // count will not be decremented again if GtkWindow's reference has already
585 : // been released.
586 0 : gtk_widget_destroy(file_chooser);
587 :
588 : #if (MOZ_WIDGET_GTK == 3)
589 0 : if (mFileChooserDelegate) {
590 : // Properly deref our acquired reference. We call this after
591 : // gtk_widget_destroy() to try and ensure that pending file info
592 : // queries caused by updating the current folder have been cancelled.
593 : // However, we do not know for certain when the callback will run after
594 : // cancelled.
595 0 : g_idle_add([](gpointer data) -> gboolean {
596 0 : g_object_unref(data);
597 0 : return G_SOURCE_REMOVE;
598 0 : }, mFileChooserDelegate);
599 0 : mFileChooserDelegate = nullptr;
600 : }
601 : #endif
602 :
603 0 : if (mCallback) {
604 0 : mCallback->Done(result);
605 0 : mCallback = nullptr;
606 : } else {
607 0 : mResult = result;
608 : }
609 0 : NS_RELEASE_THIS();
610 0 : }
|