mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
* Fix app.makeSingleInstance hanging on posix systems Wait for the IO thread to be a thing before attempting to listen on the socket Fixes #9880 * Move OnBrowserReady call to PreMainMessageLoopRun to account for timing issues on macOS * Woops, how did that happen ;) * Refactor as per @zcbenz comments Also fix issue where we run the single instance callback *not* on the UI thread, this apparently results in a hung process. * Appease the linting gods * Create watcher when message loop is ready * spec: Add test case for app.makeSingleInstance * Fix missing extension when saving a file without filters Previously, when triggering the save dialog through e.g. `<a download>` links (e.g. http://jsfiddle.net/koldev/cW7W5/), the extension was only saved if Finder was set to show all extensions by default. We now always display the extension to make sure that it is saved. If we want to keep the extension hidden, we could also populate the allowed file types array with the extension from the default filename, but that would have interfered with how we set the filters. * Try to make test less flaky * Try simpler test * Fix stdout detection * Try longer timeout on test
221 lines
7.1 KiB
Plaintext
221 lines
7.1 KiB
Plaintext
// Copyright (c) 2013 GitHub, Inc.
|
|
// Use of this source code is governed by the MIT license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "atom/browser/ui/file_dialog.h"
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <CoreServices/CoreServices.h>
|
|
|
|
#include "atom/browser/native_window.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/mac/foundation_util.h"
|
|
#include "base/mac/mac_util.h"
|
|
#include "base/mac/scoped_cftyperef.h"
|
|
#include "base/strings/sys_string_conversions.h"
|
|
|
|
namespace file_dialog {
|
|
|
|
namespace {
|
|
|
|
void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) {
|
|
NSMutableSet* file_type_set = [NSMutableSet set];
|
|
for (size_t i = 0; i < filters.size(); ++i) {
|
|
const Filter& filter = filters[i];
|
|
for (size_t j = 0; j < filter.second.size(); ++j) {
|
|
// If we meet a '*' file extension, we allow all the file types and no
|
|
// need to set the specified file types.
|
|
if (filter.second[j] == "*") {
|
|
[dialog setAllowsOtherFileTypes:YES];
|
|
return;
|
|
}
|
|
base::ScopedCFTypeRef<CFStringRef> ext_cf(
|
|
base::SysUTF8ToCFStringRef(filter.second[j]));
|
|
[file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())];
|
|
}
|
|
}
|
|
|
|
// Passing empty array to setAllowedFileTypes will cause exception.
|
|
NSArray* file_types = nil;
|
|
if ([file_type_set count])
|
|
file_types = [file_type_set allObjects];
|
|
|
|
[dialog setAllowedFileTypes:file_types];
|
|
}
|
|
|
|
void SetupDialog(NSSavePanel* dialog,
|
|
const DialogSettings& settings) {
|
|
if (!settings.title.empty())
|
|
[dialog setTitle:base::SysUTF8ToNSString(settings.title)];
|
|
|
|
if (!settings.button_label.empty())
|
|
[dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)];
|
|
|
|
if (!settings.message.empty())
|
|
[dialog setMessage:base::SysUTF8ToNSString(settings.message)];
|
|
|
|
if (!settings.name_field_label.empty())
|
|
[dialog setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)];
|
|
|
|
[dialog setShowsTagField:settings.shows_tag_field];
|
|
|
|
NSString* default_dir = nil;
|
|
NSString* default_filename = nil;
|
|
if (!settings.default_path.empty()) {
|
|
if (base::DirectoryExists(settings.default_path)) {
|
|
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
|
|
} else {
|
|
if (settings.default_path.IsAbsolute()) {
|
|
default_dir =
|
|
base::SysUTF8ToNSString(settings.default_path.DirName().value());
|
|
}
|
|
|
|
default_filename =
|
|
base::SysUTF8ToNSString(settings.default_path.BaseName().value());
|
|
}
|
|
}
|
|
|
|
if (settings.filters.empty()) {
|
|
[dialog setAllowsOtherFileTypes:YES];
|
|
} else {
|
|
// Set setAllowedFileTypes before setNameFieldStringValue as it might
|
|
// override the extension set using setNameFieldStringValue
|
|
SetAllowedFileTypes(dialog, settings.filters);
|
|
}
|
|
|
|
// Make sure the extension is always visible. Without this, the extension in
|
|
// the default filename will not be used in the saved file.
|
|
[dialog setExtensionHidden:NO];
|
|
|
|
if (default_dir)
|
|
[dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
|
|
if (default_filename)
|
|
[dialog setNameFieldStringValue:default_filename];
|
|
}
|
|
|
|
void SetupDialogForProperties(NSOpenPanel* dialog, int properties) {
|
|
[dialog setCanChooseFiles:(properties & FILE_DIALOG_OPEN_FILE)];
|
|
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
|
[dialog setCanChooseDirectories:YES];
|
|
if (properties & FILE_DIALOG_CREATE_DIRECTORY)
|
|
[dialog setCanCreateDirectories:YES];
|
|
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
|
[dialog setAllowsMultipleSelection:YES];
|
|
if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
|
|
[dialog setShowsHiddenFiles:YES];
|
|
if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES)
|
|
[dialog setResolvesAliases:NO];
|
|
if (properties & FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
|
|
[dialog setTreatsFilePackagesAsDirectories:YES];
|
|
}
|
|
|
|
// Run modal dialog with parent window and return user's choice.
|
|
int RunModalDialog(NSSavePanel* dialog, atom::NativeWindow* parent_window) {
|
|
__block int chosen = NSFileHandlingPanelCancelButton;
|
|
if (!parent_window || !parent_window->GetNativeWindow()) {
|
|
chosen = [dialog runModal];
|
|
} else {
|
|
NSWindow* window = parent_window->GetNativeWindow();
|
|
|
|
[dialog beginSheetModalForWindow:window
|
|
completionHandler:^(NSInteger c) {
|
|
chosen = c;
|
|
[NSApp stopModal];
|
|
}];
|
|
[NSApp runModalForWindow:window];
|
|
}
|
|
|
|
return chosen;
|
|
}
|
|
|
|
void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
|
|
NSArray* urls = [dialog URLs];
|
|
for (NSURL* url in urls)
|
|
if ([url isFileURL])
|
|
paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path])));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool ShowOpenDialog(const DialogSettings& settings,
|
|
std::vector<base::FilePath>* paths) {
|
|
DCHECK(paths);
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
SetupDialog(dialog, settings);
|
|
SetupDialogForProperties(dialog, settings.properties);
|
|
|
|
int chosen = RunModalDialog(dialog, settings.parent_window);
|
|
if (chosen == NSFileHandlingPanelCancelButton)
|
|
return false;
|
|
|
|
ReadDialogPaths(dialog, paths);
|
|
return true;
|
|
}
|
|
|
|
void ShowOpenDialog(const DialogSettings& settings,
|
|
const OpenDialogCallback& c) {
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
SetupDialog(dialog, settings);
|
|
SetupDialogForProperties(dialog, settings.properties);
|
|
|
|
// Duplicate the callback object here since c is a reference and gcd would
|
|
// only store the pointer, by duplication we can force gcd to store a copy.
|
|
__block OpenDialogCallback callback = c;
|
|
|
|
NSWindow* window = settings.parent_window ?
|
|
settings.parent_window->GetNativeWindow() :
|
|
nullptr;
|
|
[dialog beginSheetModalForWindow:window
|
|
completionHandler:^(NSInteger chosen) {
|
|
if (chosen == NSFileHandlingPanelCancelButton) {
|
|
callback.Run(false, std::vector<base::FilePath>());
|
|
} else {
|
|
std::vector<base::FilePath> paths;
|
|
ReadDialogPaths(dialog, &paths);
|
|
callback.Run(true, paths);
|
|
}
|
|
}];
|
|
}
|
|
|
|
bool ShowSaveDialog(const DialogSettings& settings,
|
|
base::FilePath* path) {
|
|
DCHECK(path);
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
|
|
SetupDialog(dialog, settings);
|
|
|
|
int chosen = RunModalDialog(dialog, settings.parent_window);
|
|
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
|
|
return false;
|
|
|
|
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
|
|
return true;
|
|
}
|
|
|
|
void ShowSaveDialog(const DialogSettings& settings,
|
|
const SaveDialogCallback& c) {
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
|
|
SetupDialog(dialog, settings);
|
|
[dialog setCanSelectHiddenExtension:YES];
|
|
|
|
__block SaveDialogCallback callback = c;
|
|
|
|
NSWindow* window = settings.parent_window ?
|
|
settings.parent_window->GetNativeWindow() :
|
|
nullptr;
|
|
[dialog beginSheetModalForWindow:window
|
|
completionHandler:^(NSInteger chosen) {
|
|
if (chosen == NSFileHandlingPanelCancelButton) {
|
|
callback.Run(false, base::FilePath());
|
|
} else {
|
|
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
|
callback.Run(true, base::FilePath(path));
|
|
}
|
|
}];
|
|
}
|
|
|
|
} // namespace file_dialog
|