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"
10 #include "nsReadableUtils.h"
11 #include "nsNetUtil.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";
32 * This class is an observer of NSPopUpButton selection change.
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;
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;
56 if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
57 gCallSecretHiddenFileAPI = true;
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) {
69 NSInvocation* navViewInvocation =
70 [NSInvocation invocationWithMethodSignature:navViewSignature];
71 [navViewInvocation setSelector:navViewSelector];
72 [navViewInvocation setTarget:panel];
73 [navViewInvocation invoke];
75 // get the returned nav view
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) {
87 NSInvocation* showHiddenFilesInvocation =
88 [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
89 [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
90 [showHiddenFilesInvocation setTarget:navView];
91 [showHiddenFilesInvocation setArgument:&show atIndex:2];
92 [showHiddenFilesInvocation invoke];
95 NS_OBJC_END_TRY_IGNORE_BLOCK;
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.
102 class nsFilePicker::AsyncShowFilePicker : public mozilla::Runnable {
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)) {
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
121 nsIFilePicker::ResultCode result = nsIFilePicker::returnCancel;
122 nsresult rv = mFilePicker->Show(&result);
124 NS_ERROR("FilePicker's Show() implementation failed!");
128 mCallback->Done(result);
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) {
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)) {
166 stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
167 length:locaLabel.Length()];
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];
194 [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(
196 length:currentFilter.Length()];
199 [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(
201 length:currentTitle.Length()];
203 [popupButton addItemWithTitle:titleString];
204 [titleString release];
206 if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems) {
207 [popupButton selectItemAtIndex:mSelectedTypeIndex];
209 [popupButton setTag:kSaveTypeControlTag];
210 [popupButton sizeToFit]; // we have to do sizeToFit to get the height
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
219 float greatestHeight = [textField frame].size.height;
220 if ([popupButton frame].size.height > greatestHeight) {
221 greatestHeight = [popupButton frame].size.height;
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;
233 setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
235 float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
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;
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.
263 userClicksOK = GetLocalFiles(false, mFiles);
266 case modeOpenMultiple:
267 userClicksOK = GetLocalFiles(true, mFiles);
271 userClicksOK = PutLocalFile(getter_AddRefs(theFile));
275 userClicksOK = GetLocalFolder(getter_AddRefs(theFile));
279 NS_ERROR("Unknown file picker mode");
284 mFiles.AppendObject(theFile);
287 *retval = userClicksOK;
292 nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
293 if (MaybeBlockFilePicker(aCallback)) {
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) {
329 mFilePicker->SetFilterIndex(selectedItem);
330 UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
332 NS_OBJC_END_TRY_BLOCK_RETURN();
336 // Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in
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];
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.
365 if (filters && [filters count] == 1 &&
366 [(NSString*)[filters objectAtIndex:0] isEqualToString:@"app"]) {
367 theDir = @"/Applications/";
374 [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
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]
393 selector:@selector(menuChangedItem:)
394 name:NSMenuWillSendActionNotification
397 UpdatePanelFileTypes(thePanel, filters);
398 result = [thePanel runModal];
400 [[NSNotificationCenter defaultCenter] removeObserver:observer];
403 // If we show all file types, also "expose" bundles' contents.
405 [thePanel setTreatsFilePackagesAsDirectories:YES];
407 [thePanel setAllowedFileTypes:filters];
408 result = [thePanel runModal];
410 nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
412 if (result == NSModalResponseCancel) {
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
421 for (NSURL* url in [thePanel URLs]) {
426 nsCOMPtr<nsILocalFileMac> macLocalFile;
427 if (NS_SUCCEEDED(NS_NewLocalFileWithCFURL((CFURLRef)url,
428 getter_AddRefs(macLocalFile)))) {
429 outFiles.AppendObject(macLocalFile);
433 if (outFiles.Count() > 0) {
439 NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnOK);
442 // Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in
444 nsIFilePicker::ResultCode nsFilePicker::GetLocalFolder(nsIFile** outFile) {
445 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
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();
470 [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
472 nsCocoaUtils::PrepareForNativeAppModalDialog();
473 int result = [thePanel runModal];
474 nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
476 if (result == NSModalResponseCancel) {
480 // get the path for the folder (we allow just 1, so that's all we get)
481 NSURL* theURL = [[thePanel URLs] objectAtIndex:0];
483 nsCOMPtr<nsILocalFileMac> macLocalFile;
484 if (NS_SUCCEEDED(NS_NewLocalFileWithCFURL((CFURLRef)theURL,
485 getter_AddRefs(macLocalFile)))) {
486 macLocalFile.forget(outFile);
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;
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 ];
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
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);
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];
550 // set up default directory
551 NSString* theDir = PanelDefaultDirectory();
553 [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
557 nsCocoaUtils::PrepareForNativeAppModalDialog();
558 [thePanel setNameFieldStringValue:defaultFilename];
559 int result = [thePanel runModal];
560 nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
561 if (result == NSModalResponseCancel) {
566 NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
568 mSelectedTypeIndex = [popupButton indexOfSelectedItem];
571 NSURL* fileURL = [thePanel URL];
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
580 if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
581 retVal = returnReplace;
590 NS_OBJC_END_TRY_BLOCK_RETURN(nsIFilePicker::returnCancel);
593 NSArray* nsFilePicker::GetFilterList() {
594 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
596 if (!mFilters.Length()) {
600 if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
601 NS_WARNING("An out of range index has been selected. Using the first index "
603 mSelectedTypeIndex = 0;
606 const nsString& filterWide = mFilters[mSelectedTypeIndex];
607 if (!filterWide.Length()) {
611 if (filterWide.Equals(u"*"_ns)) {
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*>(
621 length:filterWide.Length()]]
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];
631 return [[[NSArray alloc]
632 initWithArray:[filterString componentsSeparatedByString:@";"]]
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()) {
649 stringWithCharacters:(const unichar*)mOkButtonLabel.get()
650 length:mOkButtonLabel.Length()]];
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];
671 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
674 NS_IMETHODIMP nsFilePicker::GetFile(nsIFile** aFile) {
675 NS_ENSURE_ARG_POINTER(aFile);
678 // just return the first file
679 if (mFiles.Count() > 0) {
680 *aFile = mFiles.ObjectAt(0);
681 NS_IF_ADDREF(*aFile);
687 NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI** aFileURL) {
688 NS_ENSURE_ARG_POINTER(aFileURL);
691 if (mFiles.Count() == 0) {
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;
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();
717 NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension) {
721 // Append an entry to the filters array
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);
728 mFilters.AppendElement(aFilter);
730 mTitles.AppendElement(aTitle);
735 // Get the filter index - do we still need this?
736 NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
737 *aFilterIndex = mSelectedTypeIndex;
741 // Set the filter index - do we still need this?
742 NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
743 mSelectedTypeIndex = aFilterIndex;