Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / widget / cocoa / nsFilePicker.mm
blobb7e1985c3dcbfb52212394f3c2473f7a3204ac31
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 #import <Cocoa/Cocoa.h>
8 #include "nsFilePicker.h"
9 #include "nsCOMPtr.h"
10 #include "nsReadableUtils.h"
11 #include "nsNetUtil.h"
12 #include "nsIFile.h"
13 #include "nsILocalFileMac.h"
14 #include "nsArrayEnumerator.h"
15 #include "nsIStringBundle.h"
16 #include "nsCocoaUtils.h"
17 #include "nsThreadUtils.h"
18 #include "mozilla/Preferences.h"
20 // This must be included last:
21 #include "nsObjCExceptions.h"
23 using namespace mozilla;
25 const float kAccessoryViewPadding = 5;
26 const int kSaveTypeControlTag = 1;
28 static bool gCallSecretHiddenFileAPI = false;
29 const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
31 /**
32  * This class is an observer of NSPopUpButton selection change.
33  */
34 @interface NSPopUpButtonObserver : NSObject {
35   NSPopUpButton* mPopUpButton;
36   NSOpenPanel* mOpenPanel;
37   nsFilePicker* mFilePicker;
39 - (void)setPopUpButton:(NSPopUpButton*)aPopUpButton;
40 - (void)setOpenPanel:(NSOpenPanel*)aOpenPanel;
41 - (void)setFilePicker:(nsFilePicker*)aFilePicker;
42 - (void)menuChangedItem:(NSNotification*)aSender;
43 @end
45 NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
47 // We never want to call the secret show hidden files API unless the pref
48 // has been set. Once the pref has been set we always need to call it even
49 // if it disappears so that we stop showing hidden files if a user deletes
50 // the pref. If the secret API was used once and things worked out it should
51 // continue working for subsequent calls so the user is at no more risk.
52 static void SetShowHiddenFileState(NSSavePanel* panel) {
53   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
55   bool show = false;
56   if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
57     gCallSecretHiddenFileAPI = true;
58   }
60   if (gCallSecretHiddenFileAPI) {
61     // invoke a method to get a Cocoa-internal nav view
62     SEL navViewSelector = @selector(_navView);
63     NSMethodSignature* navViewSignature =
64         [panel methodSignatureForSelector:navViewSelector];
65     if (!navViewSignature) {
66       return;
67     }
69     NSInvocation* navViewInvocation =
70         [NSInvocation invocationWithMethodSignature:navViewSignature];
71     [navViewInvocation setSelector:navViewSelector];
72     [navViewInvocation setTarget:panel];
73     [navViewInvocation invoke];
75     // get the returned nav view
76     id navView = nil;
77     [navViewInvocation getReturnValue:&navView];
79     // invoke the secret show hidden file state method on the nav view
80     SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
81     NSMethodSignature* showHiddenFilesSignature =
82         [navView methodSignatureForSelector:showHiddenFilesSelector];
83     if (!showHiddenFilesSignature) {
84       return;
85     }
87     NSInvocation* showHiddenFilesInvocation =
88         [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
89     [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
90     [showHiddenFilesInvocation setTarget:navView];
91     [showHiddenFilesInvocation setArgument:&show atIndex:2];
92     [showHiddenFilesInvocation invoke];
93   }
95   NS_OBJC_END_TRY_IGNORE_BLOCK;
98 /**
99  * A runnable to dispatch from the main thread to the main thread to display
100  * the file picker while letting the showAsync method return right away.
101  */
102 class nsFilePicker::AsyncShowFilePicker : public mozilla::Runnable {
103  public:
104   AsyncShowFilePicker(nsFilePicker* aFilePicker,
105                       nsIFilePickerShownCallback* aCallback)
106       : mozilla::Runnable("AsyncShowFilePicker"),
107         mFilePicker(aFilePicker),
108         mCallback(aCallback) {}
110   NS_IMETHOD Run() override {
111     NS_ASSERTION(NS_IsMainThread(),
112                  "AsyncShowFilePicker should be on the main thread!");
114     if (mFilePicker->MaybeBlockFilePicker(mCallback)) {
115       return NS_OK;
116     }
118     // macOS requires require GUI operations to be on the main thread, so that's
119     // why we're not dispatching to another thread and calling back to the main
120     // after it's done.
121     nsIFilePicker::ResultCode result = nsIFilePicker::returnCancel;
122     nsresult rv = mFilePicker->Show(&result);
123     if (NS_FAILED(rv)) {
124       NS_ERROR("FilePicker's Show() implementation failed!");
125     }
127     if (mCallback) {
128       mCallback->Done(result);
129     }
130     return NS_OK;
131   }
133  private:
134   RefPtr<nsFilePicker> mFilePicker;
135   RefPtr<nsIFilePickerShownCallback> mCallback;
138 nsFilePicker::nsFilePicker() = default;
140 nsFilePicker::~nsFilePicker() = default;
142 void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
143   mTitle = aTitle;
146 NSView* nsFilePicker::GetAccessoryView() {
147   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
149   NSView* accessoryView =
150       [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
152   // Set a label's default value.
153   NSString* label = @"Format:";
155   // Try to get the localized string.
156   nsCOMPtr<nsIStringBundleService> sbs =
157       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
158   nsCOMPtr<nsIStringBundle> bundle;
159   nsresult rv = sbs->CreateBundle(
160       "chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
161   if (NS_SUCCEEDED(rv)) {
162     nsAutoString locaLabel;
163     rv = bundle->GetStringFromName("formatLabel", locaLabel);
164     if (NS_SUCCEEDED(rv)) {
165       label = [NSString
166           stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
167                         length:locaLabel.Length()];
168     }
169   }
171   // set up label text field
172   NSTextField* textField = [[[NSTextField alloc] init] autorelease];
173   [textField setEditable:NO];
174   [textField setSelectable:NO];
175   [textField setDrawsBackground:NO];
176   [textField setBezeled:NO];
177   [textField setBordered:NO];
178   [textField setFont:[NSFont labelFontOfSize:13.0]];
179   [textField setStringValue:label];
180   [textField setTag:0];
181   [textField sizeToFit];
183   // set up popup button
184   NSPopUpButton* popupButton =
185       [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)
186                                   pullsDown:NO] autorelease];
187   uint32_t numMenuItems = mTitles.Length();
188   for (uint32_t i = 0; i < numMenuItems; i++) {
189     const nsString& currentTitle = mTitles[i];
190     NSString* titleString;
191     if (currentTitle.IsEmpty()) {
192       const nsString& currentFilter = mFilters[i];
193       titleString =
194           [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(
195                                                    currentFilter.get())
196                                         length:currentFilter.Length()];
197     } else {
198       titleString =
199           [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(
200                                                    currentTitle.get())
201                                         length:currentTitle.Length()];
202     }
203     [popupButton addItemWithTitle:titleString];
204     [titleString release];
205   }
206   if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems) {
207     [popupButton selectItemAtIndex:mSelectedTypeIndex];
208   }
209   [popupButton setTag:kSaveTypeControlTag];
210   [popupButton sizeToFit];  // we have to do sizeToFit to get the height
211                             // calculated for us
212   // This is just a default width that works well, doesn't truncate the vast
213   // majority of things that might end up in the menu.
214   [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
216   // position everything based on control sizes with kAccessoryViewPadding pix
217   // padding on each side kAccessoryViewPadding pix horizontal padding between
218   // controls
219   float greatestHeight = [textField frame].size.height;
220   if ([popupButton frame].size.height > greatestHeight) {
221     greatestHeight = [popupButton frame].size.height;
222   }
223   float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
224   float totalViewWidth = [textField frame].size.width +
225                          [popupButton frame].size.width +
226                          kAccessoryViewPadding * 3;
227   [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
229   float textFieldOriginY =
230       ((greatestHeight - [textField frame].size.height) / 2 + 1) +
231       kAccessoryViewPadding;
232   [textField
233       setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
235   float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
236   float popupOriginY =
237       ((greatestHeight - [popupButton frame].size.height) / 2) +
238       kAccessoryViewPadding;
239   [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
241   [accessoryView addSubview:textField];
242   [accessoryView addSubview:popupButton];
243   return accessoryView;
245   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
248 // Display the file dialog
249 nsresult nsFilePicker::Show(ResultCode* retval) {
250   NS_ENSURE_ARG_POINTER(retval);
252   *retval = returnCancel;
254   ResultCode userClicksOK = returnCancel;
256   mFiles.Clear();
257   nsCOMPtr<nsIFile> theFile;
259   // Note that GetLocalFolder shares a lot of code with GetLocalFiles.
260   // Could combine the functions and just pass the mode in.
261   switch (mMode) {
262     case modeOpen:
263       userClicksOK = GetLocalFiles(false, mFiles);
264       break;
266     case modeOpenMultiple:
267       userClicksOK = GetLocalFiles(true, mFiles);
268       break;
270     case modeSave:
271       userClicksOK = PutLocalFile(getter_AddRefs(theFile));
272       break;
274     case modeGetFolder:
275       userClicksOK = GetLocalFolder(getter_AddRefs(theFile));
276       break;
278     default:
279       NS_ERROR("Unknown file picker mode");
280       break;
281   }
283   if (theFile) {
284     mFiles.AppendObject(theFile);
285   }
287   *retval = userClicksOK;
288   return NS_OK;
291 NS_IMETHODIMP
292 nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
293   if (MaybeBlockFilePicker(aCallback)) {
294     return NS_OK;
295   }
297   nsCOMPtr<nsIRunnable> filePickerEvent =
298       new AsyncShowFilePicker(this, aCallback);
299   return NS_DispatchToMainThread(filePickerEvent);
302 static void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters) {
303   // If we show all file types, also "expose" bundles' contents.
304   [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
306   [aPanel setAllowedFileTypes:aFilters];
309 @implementation NSPopUpButtonObserver
310 - (void)setPopUpButton:(NSPopUpButton*)aPopUpButton {
311   mPopUpButton = aPopUpButton;
314 - (void)setOpenPanel:(NSOpenPanel*)aOpenPanel {
315   mOpenPanel = aOpenPanel;
318 - (void)setFilePicker:(nsFilePicker*)aFilePicker {
319   mFilePicker = aFilePicker;
322 - (void)menuChangedItem:(NSNotification*)aSender {
323   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
324   int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
325   if (selectedItem < 0) {
326     return;
327   }
329   mFilePicker->SetFilterIndex(selectedItem);
330   UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
332   NS_OBJC_END_TRY_BLOCK_RETURN();
334 @end
336 // Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in
337 // the dialog.
338 nsIFilePicker::ResultCode nsFilePicker::GetLocalFiles(
339     bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles) {
340   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
342   ResultCode retVal = nsIFilePicker::returnCancel;
343   NSOpenPanel* thePanel = [NSOpenPanel openPanel];
345   SetShowHiddenFileState(thePanel);
347   // Set the options for how the get file dialog will appear
348   SetDialogTitle(mTitle, thePanel);
349   [thePanel setAllowsMultipleSelection:inAllowMultiple];
350   [thePanel setCanSelectHiddenExtension:YES];
351   [thePanel setCanChooseDirectories:NO];
352   [thePanel setCanChooseFiles:YES];
353   [thePanel setResolvesAliases:YES];
355   // Get filters
356   // filters may be null, if we should allow all file types.
357   NSArray* filters = GetFilterList();
359   // set up default directory
360   NSString* theDir = PanelDefaultDirectory();
362   // if this is the "Choose application..." dialog, and no other start
363   // dir has been set, then use the Applications folder.
364   if (!theDir) {
365     if (filters && [filters count] == 1 &&
366         [(NSString*)[filters objectAtIndex:0] isEqualToString:@"app"]) {
367       theDir = @"/Applications/";
368     } else {
369       theDir = @"";
370     }
371   }
373   if (theDir) {
374     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
375   }
377   int result;
378   nsCocoaUtils::PrepareForNativeAppModalDialog();
379   if (mFilters.Length() > 1) {
380     // [NSURL initWithString:] (below) throws an exception if URLString is nil.
382     NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
384     NSView* accessoryView = GetAccessoryView();
385     [thePanel setAccessoryView:accessoryView];
387     [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
388     [observer setOpenPanel:thePanel];
389     [observer setFilePicker:this];
391     [[NSNotificationCenter defaultCenter]
392         addObserver:observer
393            selector:@selector(menuChangedItem:)
394                name:NSMenuWillSendActionNotification
395              object:nil];
397     UpdatePanelFileTypes(thePanel, filters);
398     result = [thePanel runModal];
400     [[NSNotificationCenter defaultCenter] removeObserver:observer];
401     [observer release];
402   } else {
403     // If we show all file types, also "expose" bundles' contents.
404     if (!filters) {
405       [thePanel setTreatsFilePackagesAsDirectories:YES];
406     }
407     [thePanel setAllowedFileTypes:filters];
408     result = [thePanel runModal];
409   }
410   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
412   if (result == NSModalResponseCancel) {
413     return retVal;
414   }
416   // Converts data from a NSArray of NSURL to the returned format.
417   // We should be careful to not call [thePanel URLs] more than once given that
418   // it creates a new array each time.
419   // We are using Fast Enumeration, thus the NSURL array is created once then
420   // iterated.
421   for (NSURL* url in [thePanel URLs]) {
422     if (!url) {
423       continue;
424     }
426     nsCOMPtr<nsILocalFileMac> macLocalFile;
427     if (NS_SUCCEEDED(NS_NewLocalFileWithCFURL((CFURLRef)url,
428                                               getter_AddRefs(macLocalFile)))) {
429       outFiles.AppendObject(macLocalFile);
430     }
431   }
433   if (outFiles.Count() > 0) {
434     retVal = returnOK;
435   }
437   return retVal;
439   NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnOK);
442 // Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in
443 // the dialog.
444 nsIFilePicker::ResultCode nsFilePicker::GetLocalFolder(nsIFile** outFile) {
445   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
446   NS_ASSERTION(
447       outFile,
448       "this protected member function expects a null initialized out pointer");
450   ResultCode retVal = nsIFilePicker::returnCancel;
451   NSOpenPanel* thePanel = [NSOpenPanel openPanel];
453   SetShowHiddenFileState(thePanel);
455   // Set the options for how the get file dialog will appear
456   SetDialogTitle(mTitle, thePanel);
457   [thePanel setAllowsMultipleSelection:NO];
458   [thePanel setCanSelectHiddenExtension:YES];
459   [thePanel setCanChooseDirectories:YES];
460   [thePanel setCanChooseFiles:NO];
461   [thePanel setResolvesAliases:YES];
462   [thePanel setCanCreateDirectories:YES];
464   // packages != folders
465   [thePanel setTreatsFilePackagesAsDirectories:NO];
467   // set up default directory
468   NSString* theDir = PanelDefaultDirectory();
469   if (theDir) {
470     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
471   }
472   nsCocoaUtils::PrepareForNativeAppModalDialog();
473   int result = [thePanel runModal];
474   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
476   if (result == NSModalResponseCancel) {
477     return retVal;
478   }
480   // get the path for the folder (we allow just 1, so that's all we get)
481   NSURL* theURL = [[thePanel URLs] objectAtIndex:0];
482   if (theURL) {
483     nsCOMPtr<nsILocalFileMac> macLocalFile;
484     if (NS_SUCCEEDED(NS_NewLocalFileWithCFURL((CFURLRef)theURL,
485                                               getter_AddRefs(macLocalFile)))) {
486       macLocalFile.forget(outFile);
487       retVal = returnOK;
488     }
489   }
491   return retVal;
493   NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnOK);
496 // Returns |returnOK| if the user presses OK in the dialog.
497 nsIFilePicker::ResultCode nsFilePicker::PutLocalFile(nsIFile** outFile) {
498   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
499   NS_ASSERTION(
500       outFile,
501       "this protected member function expects a null initialized out pointer");
503   ResultCode retVal = nsIFilePicker::returnCancel;
504   NSSavePanel* thePanel = [NSSavePanel savePanel];
506   SetShowHiddenFileState(thePanel);
508   SetDialogTitle(mTitle, thePanel);
510   // set up accessory view for file format options
511   NSView* accessoryView = GetAccessoryView();
512   [thePanel setAccessoryView:accessoryView];
514   // set up default file name
515   NSString* defaultFilename =
516       [NSString stringWithCharacters:(const unichar*)mDefaultFilename.get()
517                               length:mDefaultFilename.Length()];
519   // Set up the allowed type. This prevents the extension from being selected.
520   NSString* extension = defaultFilename.pathExtension;
521   if (extension.length != 0) {
522     thePanel.allowedFileTypes = @[ extension ];
523   }
524   // Allow users to change the extension.
525   thePanel.allowsOtherFileTypes = YES;
527   // If extensions are hidden and we’re saving a file with multiple extensions,
528   // only the last extension will be hidden in the panel (".tar.gz" will become
529   // ".tar"). If the remaining extension is known, the OS will think that we're
530   // trying to add a non-default extension. To avoid the confusion, we ensure
531   // that all extensions are shown in the panel if the remaining extension is
532   // known by the OS.
533   NSString* fileName =
534       [[defaultFilename lastPathComponent] stringByDeletingPathExtension];
535   NSString* otherExtension = fileName.pathExtension;
536   if (otherExtension.length != 0) {
537     // There's another extension here. Get the UTI.
538     CFStringRef type = UTTypeCreatePreferredIdentifierForTag(
539         kUTTagClassFilenameExtension, (CFStringRef)otherExtension, NULL);
540     if (type) {
541       if (!CFStringHasPrefix(type, CFSTR("dyn."))) {
542         // We have a UTI, otherwise the type would have a "dyn." prefix. Ensure
543         // extensions are shown in the panel.
544         [thePanel setExtensionHidden:NO];
545       }
546       CFRelease(type);
547     }
548   }
550   // set up default directory
551   NSString* theDir = PanelDefaultDirectory();
552   if (theDir) {
553     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
554   }
556   // load the panel
557   nsCocoaUtils::PrepareForNativeAppModalDialog();
558   [thePanel setNameFieldStringValue:defaultFilename];
559   int result = [thePanel runModal];
560   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
561   if (result == NSModalResponseCancel) {
562     return retVal;
563   }
565   // get the save type
566   NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
567   if (popupButton) {
568     mSelectedTypeIndex = [popupButton indexOfSelectedItem];
569   }
571   NSURL* fileURL = [thePanel URL];
572   if (fileURL) {
573     nsCOMPtr<nsILocalFileMac> macLocalFile;
574     if (NS_SUCCEEDED(NS_NewLocalFileWithCFURL((CFURLRef)fileURL,
575                                               getter_AddRefs(macLocalFile)))) {
576       macLocalFile.forget(outFile);
577       // We tell if we are replacing or not by just looking to see if the file
578       // exists. The user could not have hit OK and not meant to replace the
579       // file.
580       if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
581         retVal = returnReplace;
582       } else {
583         retVal = returnOK;
584       }
585     }
586   }
588   return retVal;
590   NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnCancel);
593 NSArray* nsFilePicker::GetFilterList() {
594   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
596   if (!mFilters.Length()) {
597     return nil;
598   }
600   if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
601     NS_WARNING("An out of range index has been selected. Using the first index "
602                "instead.");
603     mSelectedTypeIndex = 0;
604   }
606   const nsString& filterWide = mFilters[mSelectedTypeIndex];
607   if (!filterWide.Length()) {
608     return nil;
609   }
611   if (filterWide.Equals(u"*"_ns)) {
612     return nil;
613   }
615   // The extensions in filterWide are in the format "*.ext" but are expected
616   // in the format "ext" by NSOpenPanel. So we need to filter some characters.
617   NSMutableString* filterString = [[[NSMutableString alloc]
618       initWithString:[NSString
619                          stringWithCharacters:reinterpret_cast<const unichar*>(
620                                                   filterWide.get())
621                                        length:filterWide.Length()]]
622       autorelease];
623   NSCharacterSet* set =
624       [NSCharacterSet characterSetWithCharactersInString:@". *"];
625   NSRange range = [filterString rangeOfCharacterFromSet:set];
626   while (range.length) {
627     [filterString replaceCharactersInRange:range withString:@""];
628     range = [filterString rangeOfCharacterFromSet:set];
629   }
631   return [[[NSArray alloc]
632       initWithArray:[filterString componentsSeparatedByString:@";"]]
633       autorelease];
635   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
638 // Sets the dialog title to whatever it should be.  If it fails, eh,
639 // the OS will provide a sensible default.
640 void nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel) {
641   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
643   [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get()
644                                            length:inTitle.Length()]];
646   if (!mOkButtonLabel.IsEmpty()) {
647     [aPanel
648         setPrompt:[NSString
649                       stringWithCharacters:(const unichar*)mOkButtonLabel.get()
650                                     length:mOkButtonLabel.Length()]];
651   }
653   NS_OBJC_END_TRY_IGNORE_BLOCK;
656 // Converts path from an nsIFile into a NSString path
657 // If it fails, returns an empty string.
658 NSString* nsFilePicker::PanelDefaultDirectory() {
659   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
661   NSString* directory = nil;
662   if (mDisplayDirectory) {
663     nsAutoString pathStr;
664     mDisplayDirectory->GetPath(pathStr);
665     directory = [[[NSString alloc]
666         initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
667                     length:pathStr.Length()] autorelease];
668   }
669   return directory;
671   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
674 NS_IMETHODIMP nsFilePicker::GetFile(nsIFile** aFile) {
675   NS_ENSURE_ARG_POINTER(aFile);
676   *aFile = nullptr;
678   // just return the first file
679   if (mFiles.Count() > 0) {
680     *aFile = mFiles.ObjectAt(0);
681     NS_IF_ADDREF(*aFile);
682   }
684   return NS_OK;
687 NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI** aFileURL) {
688   NS_ENSURE_ARG_POINTER(aFileURL);
689   *aFileURL = nullptr;
691   if (mFiles.Count() == 0) {
692     return NS_OK;
693   }
695   return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
698 NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
699   return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
702 NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString) {
703   mDefaultFilename = aString;
704   return NS_OK;
707 NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString) {
708   return NS_ERROR_FAILURE;
711 // The default extension to use for files
712 NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension) {
713   aExtension.Truncate();
714   return NS_OK;
717 NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension) {
718   return NS_OK;
721 // Append an entry to the filters array
722 NS_IMETHODIMP
723 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
724   // "..apps" has to be translated with native executable extensions.
725   if (aFilter.EqualsLiteral("..apps")) {
726     mFilters.AppendElement(u"*.app"_ns);
727   } else {
728     mFilters.AppendElement(aFilter);
729   }
730   mTitles.AppendElement(aTitle);
732   return NS_OK;
735 // Get the filter index - do we still need this?
736 NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
737   *aFilterIndex = mSelectedTypeIndex;
738   return NS_OK;
741 // Set the filter index - do we still need this?
742 NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
743   mSelectedTypeIndex = aFilterIndex;
744   return NS_OK;