Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsFilePicker.mm
blob31c64c5367748c09f97a0f2ad9370ca3f244686e
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 "mozilla/Preferences.h"
19 // This must be included last:
20 #include "nsObjCExceptions.h"
22 using namespace mozilla;
24 const float kAccessoryViewPadding = 5;
25 const int kSaveTypeControlTag = 1;
27 static bool gCallSecretHiddenFileAPI = false;
28 const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
30 /**
31  * This class is an observer of NSPopUpButton selection change.
32  */
33 @interface NSPopUpButtonObserver : NSObject {
34   NSPopUpButton* mPopUpButton;
35   NSOpenPanel* mOpenPanel;
36   nsFilePicker* mFilePicker;
38 - (void)setPopUpButton:(NSPopUpButton*)aPopUpButton;
39 - (void)setOpenPanel:(NSOpenPanel*)aOpenPanel;
40 - (void)setFilePicker:(nsFilePicker*)aFilePicker;
41 - (void)menuChangedItem:(NSNotification*)aSender;
42 @end
44 NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
46 // We never want to call the secret show hidden files API unless the pref
47 // has been set. Once the pref has been set we always need to call it even
48 // if it disappears so that we stop showing hidden files if a user deletes
49 // the pref. If the secret API was used once and things worked out it should
50 // continue working for subsequent calls so the user is at no more risk.
51 static void SetShowHiddenFileState(NSSavePanel* panel) {
52   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
54   bool show = false;
55   if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
56     gCallSecretHiddenFileAPI = true;
57   }
59   if (gCallSecretHiddenFileAPI) {
60     // invoke a method to get a Cocoa-internal nav view
61     SEL navViewSelector = @selector(_navView);
62     NSMethodSignature* navViewSignature =
63         [panel methodSignatureForSelector:navViewSelector];
64     if (!navViewSignature) return;
65     NSInvocation* navViewInvocation =
66         [NSInvocation invocationWithMethodSignature:navViewSignature];
67     [navViewInvocation setSelector:navViewSelector];
68     [navViewInvocation setTarget:panel];
69     [navViewInvocation invoke];
71     // get the returned nav view
72     id navView = nil;
73     [navViewInvocation getReturnValue:&navView];
75     // invoke the secret show hidden file state method on the nav view
76     SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
77     NSMethodSignature* showHiddenFilesSignature =
78         [navView methodSignatureForSelector:showHiddenFilesSelector];
79     if (!showHiddenFilesSignature) return;
80     NSInvocation* showHiddenFilesInvocation =
81         [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
82     [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
83     [showHiddenFilesInvocation setTarget:navView];
84     [showHiddenFilesInvocation setArgument:&show atIndex:2];
85     [showHiddenFilesInvocation invoke];
86   }
88   NS_OBJC_END_TRY_IGNORE_BLOCK;
91 nsFilePicker::nsFilePicker() : mSelectedTypeIndex(0) {}
93 nsFilePicker::~nsFilePicker() {}
95 void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
96   mTitle = aTitle;
99 NSView* nsFilePicker::GetAccessoryView() {
100   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
102   NSView* accessoryView =
103       [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
105   // Set a label's default value.
106   NSString* label = @"Format:";
108   // Try to get the localized string.
109   nsCOMPtr<nsIStringBundleService> sbs =
110       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
111   nsCOMPtr<nsIStringBundle> bundle;
112   nsresult rv = sbs->CreateBundle(
113       "chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
114   if (NS_SUCCEEDED(rv)) {
115     nsAutoString locaLabel;
116     rv = bundle->GetStringFromName("formatLabel", locaLabel);
117     if (NS_SUCCEEDED(rv)) {
118       label = [NSString
119           stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
120                         length:locaLabel.Length()];
121     }
122   }
124   // set up label text field
125   NSTextField* textField = [[[NSTextField alloc] init] autorelease];
126   [textField setEditable:NO];
127   [textField setSelectable:NO];
128   [textField setDrawsBackground:NO];
129   [textField setBezeled:NO];
130   [textField setBordered:NO];
131   [textField setFont:[NSFont labelFontOfSize:13.0]];
132   [textField setStringValue:label];
133   [textField setTag:0];
134   [textField sizeToFit];
136   // set up popup button
137   NSPopUpButton* popupButton =
138       [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)
139                                   pullsDown:NO] autorelease];
140   uint32_t numMenuItems = mTitles.Length();
141   for (uint32_t i = 0; i < numMenuItems; i++) {
142     const nsString& currentTitle = mTitles[i];
143     NSString* titleString;
144     if (currentTitle.IsEmpty()) {
145       const nsString& currentFilter = mFilters[i];
146       titleString =
147           [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(
148                                                    currentFilter.get())
149                                         length:currentFilter.Length()];
150     } else {
151       titleString =
152           [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(
153                                                    currentTitle.get())
154                                         length:currentTitle.Length()];
155     }
156     [popupButton addItemWithTitle:titleString];
157     [titleString release];
158   }
159   if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
160     [popupButton selectItemAtIndex:mSelectedTypeIndex];
161   [popupButton setTag:kSaveTypeControlTag];
162   [popupButton sizeToFit];  // we have to do sizeToFit to get the height
163                             // calculated for us
164   // This is just a default width that works well, doesn't truncate the vast
165   // majority of things that might end up in the menu.
166   [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
168   // position everything based on control sizes with kAccessoryViewPadding pix
169   // padding on each side kAccessoryViewPadding pix horizontal padding between
170   // controls
171   float greatestHeight = [textField frame].size.height;
172   if ([popupButton frame].size.height > greatestHeight)
173     greatestHeight = [popupButton frame].size.height;
174   float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
175   float totalViewWidth = [textField frame].size.width +
176                          [popupButton frame].size.width +
177                          kAccessoryViewPadding * 3;
178   [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
180   float textFieldOriginY =
181       ((greatestHeight - [textField frame].size.height) / 2 + 1) +
182       kAccessoryViewPadding;
183   [textField
184       setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
186   float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
187   float popupOriginY =
188       ((greatestHeight - [popupButton frame].size.height) / 2) +
189       kAccessoryViewPadding;
190   [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
192   [accessoryView addSubview:textField];
193   [accessoryView addSubview:popupButton];
194   return accessoryView;
196   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
199 // Display the file dialog
200 nsresult nsFilePicker::Show(ResultCode* retval) {
201   NS_ENSURE_ARG_POINTER(retval);
203   *retval = returnCancel;
205   ResultCode userClicksOK = returnCancel;
207   mFiles.Clear();
208   nsCOMPtr<nsIFile> theFile;
210   // Note that GetLocalFolder shares a lot of code with GetLocalFiles.
211   // Could combine the functions and just pass the mode in.
212   switch (mMode) {
213     case modeOpen:
214       userClicksOK = GetLocalFiles(false, mFiles);
215       break;
217     case modeOpenMultiple:
218       userClicksOK = GetLocalFiles(true, mFiles);
219       break;
221     case modeSave:
222       userClicksOK = PutLocalFile(getter_AddRefs(theFile));
223       break;
225     case modeGetFolder:
226       userClicksOK = GetLocalFolder(getter_AddRefs(theFile));
227       break;
229     default:
230       NS_ERROR("Unknown file picker mode");
231       break;
232   }
234   if (theFile) mFiles.AppendObject(theFile);
236   *retval = userClicksOK;
237   return NS_OK;
240 static void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters) {
241   // If we show all file types, also "expose" bundles' contents.
242   [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
244   [aPanel setAllowedFileTypes:aFilters];
247 @implementation NSPopUpButtonObserver
248 - (void)setPopUpButton:(NSPopUpButton*)aPopUpButton {
249   mPopUpButton = aPopUpButton;
252 - (void)setOpenPanel:(NSOpenPanel*)aOpenPanel {
253   mOpenPanel = aOpenPanel;
256 - (void)setFilePicker:(nsFilePicker*)aFilePicker {
257   mFilePicker = aFilePicker;
260 - (void)menuChangedItem:(NSNotification*)aSender {
261   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
262   int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
263   if (selectedItem < 0) {
264     return;
265   }
267   mFilePicker->SetFilterIndex(selectedItem);
268   UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
270   NS_OBJC_END_TRY_BLOCK_RETURN();
272 @end
274 // Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in
275 // the dialog.
276 nsIFilePicker::ResultCode nsFilePicker::GetLocalFiles(
277     bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles) {
278   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
280   ResultCode retVal = nsIFilePicker::returnCancel;
281   NSOpenPanel* thePanel = [NSOpenPanel openPanel];
283   SetShowHiddenFileState(thePanel);
285   // Set the options for how the get file dialog will appear
286   SetDialogTitle(mTitle, thePanel);
287   [thePanel setAllowsMultipleSelection:inAllowMultiple];
288   [thePanel setCanSelectHiddenExtension:YES];
289   [thePanel setCanChooseDirectories:NO];
290   [thePanel setCanChooseFiles:YES];
291   [thePanel setResolvesAliases:YES];
293   // Get filters
294   // filters may be null, if we should allow all file types.
295   NSArray* filters = GetFilterList();
297   // set up default directory
298   NSString* theDir = PanelDefaultDirectory();
300   // if this is the "Choose application..." dialog, and no other start
301   // dir has been set, then use the Applications folder.
302   if (!theDir) {
303     if (filters && [filters count] == 1 &&
304         [(NSString*)[filters objectAtIndex:0] isEqualToString:@"app"])
305       theDir = @"/Applications/";
306     else
307       theDir = @"";
308   }
310   if (theDir) {
311     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
312   }
314   int result;
315   nsCocoaUtils::PrepareForNativeAppModalDialog();
316   if (mFilters.Length() > 1) {
317     // [NSURL initWithString:] (below) throws an exception if URLString is nil.
319     NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
321     NSView* accessoryView = GetAccessoryView();
322     [thePanel setAccessoryView:accessoryView];
324     [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
325     [observer setOpenPanel:thePanel];
326     [observer setFilePicker:this];
328     [[NSNotificationCenter defaultCenter]
329         addObserver:observer
330            selector:@selector(menuChangedItem:)
331                name:NSMenuWillSendActionNotification
332              object:nil];
334     UpdatePanelFileTypes(thePanel, filters);
335     result = [thePanel runModal];
337     [[NSNotificationCenter defaultCenter] removeObserver:observer];
338     [observer release];
339   } else {
340     // If we show all file types, also "expose" bundles' contents.
341     if (!filters) {
342       [thePanel setTreatsFilePackagesAsDirectories:YES];
343     }
344     [thePanel setAllowedFileTypes:filters];
345     result = [thePanel runModal];
346   }
347   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
349   if (result == NSModalResponseCancel) return retVal;
351   // Converts data from a NSArray of NSURL to the returned format.
352   // We should be careful to not call [thePanel URLs] more than once given that
353   // it creates a new array each time.
354   // We are using Fast Enumeration, thus the NSURL array is created once then
355   // iterated.
356   for (NSURL* url in [thePanel URLs]) {
357     if (!url) {
358       continue;
359     }
361     nsCOMPtr<nsIFile> localFile;
362     NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
363     nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
364     if (macLocalFile &&
365         NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
366       outFiles.AppendObject(localFile);
367     }
368   }
370   if (outFiles.Count() > 0) retVal = returnOK;
372   return retVal;
374   NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnOK);
377 // Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in
378 // the dialog.
379 nsIFilePicker::ResultCode nsFilePicker::GetLocalFolder(nsIFile** outFile) {
380   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
381   NS_ASSERTION(
382       outFile,
383       "this protected member function expects a null initialized out pointer");
385   ResultCode retVal = nsIFilePicker::returnCancel;
386   NSOpenPanel* thePanel = [NSOpenPanel openPanel];
388   SetShowHiddenFileState(thePanel);
390   // Set the options for how the get file dialog will appear
391   SetDialogTitle(mTitle, thePanel);
392   [thePanel setAllowsMultipleSelection:NO];
393   [thePanel setCanSelectHiddenExtension:YES];
394   [thePanel setCanChooseDirectories:YES];
395   [thePanel setCanChooseFiles:NO];
396   [thePanel setResolvesAliases:YES];
397   [thePanel setCanCreateDirectories:YES];
399   // packages != folders
400   [thePanel setTreatsFilePackagesAsDirectories:NO];
402   // set up default directory
403   NSString* theDir = PanelDefaultDirectory();
404   if (theDir) {
405     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
406   }
407   nsCocoaUtils::PrepareForNativeAppModalDialog();
408   int result = [thePanel runModal];
409   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
411   if (result == NSModalResponseCancel) return retVal;
413   // get the path for the folder (we allow just 1, so that's all we get)
414   NSURL* theURL = [[thePanel URLs] objectAtIndex:0];
415   if (theURL) {
416     nsCOMPtr<nsIFile> localFile;
417     NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
418     nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
419     if (macLocalFile &&
420         NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
421       *outFile = localFile;
422       NS_ADDREF(*outFile);
423       retVal = returnOK;
424     }
425   }
427   return retVal;
429   NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnOK);
432 // Returns |returnOK| if the user presses OK in the dialog.
433 nsIFilePicker::ResultCode nsFilePicker::PutLocalFile(nsIFile** outFile) {
434   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
435   NS_ASSERTION(
436       outFile,
437       "this protected member function expects a null initialized out pointer");
439   ResultCode retVal = nsIFilePicker::returnCancel;
440   NSSavePanel* thePanel = [NSSavePanel savePanel];
442   SetShowHiddenFileState(thePanel);
444   SetDialogTitle(mTitle, thePanel);
446   // set up accessory view for file format options
447   NSView* accessoryView = GetAccessoryView();
448   [thePanel setAccessoryView:accessoryView];
450   // set up default file name
451   NSString* defaultFilename =
452       [NSString stringWithCharacters:(const unichar*)mDefaultFilename.get()
453                               length:mDefaultFilename.Length()];
455   // Set up the allowed type. This prevents the extension from being selected.
456   NSString* extension = defaultFilename.pathExtension;
457   if (extension.length != 0) {
458     thePanel.allowedFileTypes = @[ extension ];
459   }
460   // Allow users to change the extension.
461   thePanel.allowsOtherFileTypes = YES;
463   // If extensions are hidden and we’re saving a file with multiple extensions,
464   // only the last extension will be hidden in the panel (".tar.gz" will become
465   // ".tar"). If the remaining extension is known, the OS will think that we're
466   // trying to add a non-default extension. To avoid the confusion, we ensure
467   // that all extensions are shown in the panel if the remaining extension is
468   // known by the OS.
469   NSString* fileName =
470       [[defaultFilename lastPathComponent] stringByDeletingPathExtension];
471   NSString* otherExtension = fileName.pathExtension;
472   if (otherExtension.length != 0) {
473     // There's another extension here. Get the UTI.
474     CFStringRef type = UTTypeCreatePreferredIdentifierForTag(
475         kUTTagClassFilenameExtension, (CFStringRef)otherExtension, NULL);
476     if (type) {
477       if (!CFStringHasPrefix(type, CFSTR("dyn."))) {
478         // We have a UTI, otherwise the type would have a "dyn." prefix. Ensure
479         // extensions are shown in the panel.
480         [thePanel setExtensionHidden:NO];
481       }
482       CFRelease(type);
483     }
484   }
486   // set up default directory
487   NSString* theDir = PanelDefaultDirectory();
488   if (theDir) {
489     [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
490   }
492   // load the panel
493   nsCocoaUtils::PrepareForNativeAppModalDialog();
494   [thePanel setNameFieldStringValue:defaultFilename];
495   int result = [thePanel runModal];
496   nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
497   if (result == NSModalResponseCancel) return retVal;
499   // get the save type
500   NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
501   if (popupButton) {
502     mSelectedTypeIndex = [popupButton indexOfSelectedItem];
503   }
505   NSURL* fileURL = [thePanel URL];
506   if (fileURL) {
507     nsCOMPtr<nsIFile> localFile;
508     NS_NewLocalFile(u""_ns, true, getter_AddRefs(localFile));
509     nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
510     if (macLocalFile &&
511         NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
512       *outFile = localFile;
513       NS_ADDREF(*outFile);
514       // We tell if we are replacing or not by just looking to see if the file
515       // exists. The user could not have hit OK and not meant to replace the
516       // file.
517       if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
518         retVal = returnReplace;
519       else
520         retVal = returnOK;
521     }
522   }
524   return retVal;
526   NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnCancel);
529 NSArray* nsFilePicker::GetFilterList() {
530   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
532   if (!mFilters.Length()) {
533     return nil;
534   }
536   if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
537     NS_WARNING("An out of range index has been selected. Using the first index "
538                "instead.");
539     mSelectedTypeIndex = 0;
540   }
542   const nsString& filterWide = mFilters[mSelectedTypeIndex];
543   if (!filterWide.Length()) {
544     return nil;
545   }
547   if (filterWide.Equals(u"*"_ns)) {
548     return nil;
549   }
551   // The extensions in filterWide are in the format "*.ext" but are expected
552   // in the format "ext" by NSOpenPanel. So we need to filter some characters.
553   NSMutableString* filterString = [[[NSMutableString alloc]
554       initWithString:[NSString
555                          stringWithCharacters:reinterpret_cast<const unichar*>(
556                                                   filterWide.get())
557                                        length:filterWide.Length()]]
558       autorelease];
559   NSCharacterSet* set =
560       [NSCharacterSet characterSetWithCharactersInString:@". *"];
561   NSRange range = [filterString rangeOfCharacterFromSet:set];
562   while (range.length) {
563     [filterString replaceCharactersInRange:range withString:@""];
564     range = [filterString rangeOfCharacterFromSet:set];
565   }
567   return [[[NSArray alloc]
568       initWithArray:[filterString componentsSeparatedByString:@";"]]
569       autorelease];
571   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
574 // Sets the dialog title to whatever it should be.  If it fails, eh,
575 // the OS will provide a sensible default.
576 void nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel) {
577   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
579   [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get()
580                                            length:inTitle.Length()]];
582   if (!mOkButtonLabel.IsEmpty()) {
583     [aPanel
584         setPrompt:[NSString
585                       stringWithCharacters:(const unichar*)mOkButtonLabel.get()
586                                     length:mOkButtonLabel.Length()]];
587   }
589   NS_OBJC_END_TRY_IGNORE_BLOCK;
592 // Converts path from an nsIFile into a NSString path
593 // If it fails, returns an empty string.
594 NSString* nsFilePicker::PanelDefaultDirectory() {
595   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
597   NSString* directory = nil;
598   if (mDisplayDirectory) {
599     nsAutoString pathStr;
600     mDisplayDirectory->GetPath(pathStr);
601     directory = [[[NSString alloc]
602         initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
603                     length:pathStr.Length()] autorelease];
604   }
605   return directory;
607   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
610 NS_IMETHODIMP nsFilePicker::GetFile(nsIFile** aFile) {
611   NS_ENSURE_ARG_POINTER(aFile);
612   *aFile = nullptr;
614   // just return the first file
615   if (mFiles.Count() > 0) {
616     *aFile = mFiles.ObjectAt(0);
617     NS_IF_ADDREF(*aFile);
618   }
620   return NS_OK;
623 NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI** aFileURL) {
624   NS_ENSURE_ARG_POINTER(aFileURL);
625   *aFileURL = nullptr;
627   if (mFiles.Count() == 0) return NS_OK;
629   return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
632 NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
633   return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
636 NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString) {
637   mDefaultFilename = aString;
638   return NS_OK;
641 NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString) {
642   return NS_ERROR_FAILURE;
645 // The default extension to use for files
646 NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension) {
647   aExtension.Truncate();
648   return NS_OK;
651 NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension) {
652   return NS_OK;
655 // Append an entry to the filters array
656 NS_IMETHODIMP
657 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
658   // "..apps" has to be translated with native executable extensions.
659   if (aFilter.EqualsLiteral("..apps")) {
660     mFilters.AppendElement(u"*.app"_ns);
661   } else {
662     mFilters.AppendElement(aFilter);
663   }
664   mTitles.AppendElement(aTitle);
666   return NS_OK;
669 // Get the filter index - do we still need this?
670 NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
671   *aFilterIndex = mSelectedTypeIndex;
672   return NS_OK;
675 // Set the filter index - do we still need this?
676 NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
677   mSelectedTypeIndex = aFilterIndex;
678   return NS_OK;