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