electron/shell/browser/ui/file_dialog_mac.mm

475 lines
16 KiB
Text
Raw Normal View History

// Copyright (c) 2013 GitHub, Inc.
2014-04-25 09:49:37 +00:00
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/file_dialog.h"
#include <string>
#include <utility>
#include <vector>
#import <Cocoa/Cocoa.h>
2014-03-16 01:37:04 +00:00
#import <CoreServices/CoreServices.h>
#include "base/files/file_util.h"
2015-03-11 00:00:55 +00:00
#include "base/mac/foundation_util.h"
2014-08-06 05:47:44 +00:00
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/post_task.h"
chore: bump chromium to 99.0.4767.0 (main) (#31986) * chore: bump chromium in DEPS to 98.0.4726.0 * 3292117: Remove unneeded base/compiler_specific.h includes in //chrome. https://chromium-review.googlesource.com/c/chromium/src/+/3292117 * 3289198: Enables calculating line, word and sentence boundaries on the browser https://chromium-review.googlesource.com/c/chromium/src/+/3289198 * 3276176: Remove expired gdi-text-printing flag and associated code. https://chromium-review.googlesource.com/c/chromium/src/+/3276176 * 3240963: content: allow embedder to prevent locking scheme registry https://chromium-review.googlesource.com/c/chromium/src/+/3240963 * 3269899: Rename WebContentsImpl::GetFrameTree to GetPrimaryFrameTree https://chromium-review.googlesource.com/c/chromium/src/+/3269899 * chore: fixup patch indices * 3276279: Enable -Wshadow by default for the "chromium code" config. https://chromium-review.googlesource.com/c/chromium/src/+/3276279 * 3279737: appcache: Remove WebPreference/WebSetting https://chromium-review.googlesource.com/c/chromium/src/+/3279737 * 3275564: [api] Advance API deprecation for APIs last marked in v9.6 https://chromium-review.googlesource.com/c/v8/v8/+/3275564 * 3261873: Clean up WebScriptSource constructors https://chromium-review.googlesource.com/c/chromium/src/+/3261873 * 3279346: appcache: Remove ConsoleMessage appcache field https://chromium-review.googlesource.com/c/chromium/src/+/3279346 * 3264212: Move legacy file loading to legacy_test_runner https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3264212 Both Persistence and UI have been removed from globals, but the issues they seemed to be patching are no longer reproducible from what I can tell, and so we can just delete these and re-evaluate if something surfaces. * 3290415: x11: remove the USE_X11 define. https://chromium-review.googlesource.com/c/chromium/src/+/3290415 * chore: bump Chromium to 98.0.4728.0 * 3179530: Defer system calls in PrintingContext for OOP printing https://chromium-review.googlesource.com/c/chromium/src/+/3179530 * 3299445: Consolidate is_win conditionals in chrome/test/BUILD.gn. https://chromium-review.googlesource.com/c/chromium/src/+/3299445 * chore: update patch indices * 3223975: Break PrintJobWorker OOP logic into separate class https://chromium-review.googlesource.com/c/chromium/src/+/3223975 * chore: bump chromium in DEPS to 98.0.4730.0 * 3279001: Remove support for font-family: -webkit-pictograph https://chromium-review.googlesource.com/c/chromium/src/+/3279001 * chore: fixup patch indices * chore: bump chromium in DEPS to 98.0.4732.0 * chore: update patches * chore: bump chromium in DEPS to 98.0.4734.0 * chore: bump chromium in DEPS to 98.0.4736.0 * chore: update patches * chore: update printing patch for miracle ptr * chore: add noexcept to fix clang error * chore: bump chromium in DEPS to 98.0.4738.0 * chore: update patches * chore: bump chromium in DEPS to 98.0.4740.0 * chore: bump chromium in DEPS to 98.0.4742.0 * chore: bump chromium in DEPS to 98.0.4744.0 * chore: bump chromium in DEPS to 98.0.4746.0 * chore: bump chromium in DEPS to 98.0.4748.0 * chore: bump chromium in DEPS to 98.0.4750.0 * chore: update patches * 3293841: Remove File Handling permissions code Ref: https://chromium-review.googlesource.com/c/chromium/src/+/3293841 * chore: update patches 3311700: Move the PpapiPluginSandboxedProcessLauncherDelegate | https://chromium-review.googlesource.com/c/chromium/src/+/3311700 * 3289260: [CodeHealth]: Remove uses of Notification Service Ref: https://chromium-review.googlesource.com/c/chromium/src/+/3289260 * 3301600: Disable scripted print in fenced frames Ref: https://chromium-review.googlesource.com/c/chromium/src/+/3301600 * chore: add missing thread_restrictions headers * 3305132: Rewrite most `Foo* field_` pointer fields to `raw_ptr<Foo> field_`. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/3305132 * fix: add ppapi_sandbox header for linux 3311700: Move the PpapiPluginSandboxedProcessLauncherDelegate | https://chromium-review.googlesource.com/c/chromium/src/+/3311700 * chore: manually bump chromium in DEPS to 98.0.4757.0 * chore: update patches * 3321044: Remove DictionaryValue::Clear() Ref: https://chromium-review.googlesource.com/c/chromium/src/+/3321044 * chore: update printing.patch Refs: - 3304556: [code health] Remove notification observation from PrintJob. | https://chromium-review.googlesource.com/c/chromium/src/+/3304556 - 3305095: [code health] Remove NotificationService from PrintViewManagerBase. | https://chromium-review.googlesource.com/c/chromium/src/+/3305095 * build: add v8-embedder-state headers to GN patch * chore: bump chromium in DEPS to 99.0.4767.0 * chore: update patches * chore: rename CookiePartitionKeychain ...to CookiePartitionKeyCollection * chore: update video consumers * refactor: use newer base::Value API * 3232598: Convert net::DnsOverHttpsServerConfig into a class | https://chromium-review.googlesource.com/c/chromium/src/+/3232598 * 3327865: Remove the default WebContentsUserData ctor. | https://chromium-review.googlesource.com/c/chromium/src/+/3327865 * 3302814: DevTools: Add getPreference binding | https://chromium-review.googlesource.com/c/chromium/src/+/3302814 * 3301474: [tq][runtime] Use build flags for JS context promise hooks | https://chromium-review.googlesource.com/c/v8/v8/+/3301474 * oops 😵‍💫 * 3272411: Reland "base/allocator: Enable PartitionAlloc-Everywhere on macOS" | https://chromium-review.googlesource.com/c/chromium/src/+/3272411 build: turn PartitionAlloc back off on mac for now * fix: WCO method got renamed * 3344749: Revert "Stop using NSRunLoop in renderer process" https://chromium-review.googlesource.com/c/chromium/src/+/3344749 * 3288746: [serial] Fix BluetoothSerialDeviceEnumerator threading issues. https://chromium-review.googlesource.com/c/chromium/src/+/3288746 * Revert "3288746: [serial] Fix BluetoothSerialDeviceEnumerator threading issues." This reverts commit 5cc69f102e43ca72ac9ef45063711bcc7d849740. * chore: disable serial device enumerator sequence dcheck * fix: comment out line in DeviceService dtor * fixup! 3279001: Remove support for font-family: -webkit-pictograph * fixup! 3279346: appcache: Remove ConsoleMessage appcache field * chore: update patches after rebase Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com> Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com> Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com> Co-authored-by: VerteDinde <khammond@slack-corp.com> Co-authored-by: clavin <clavin@electronjs.org> Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org> Co-authored-by: Jeremy Rose <jeremya@chromium.org>
2022-01-10 22:31:39 +00:00
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "shell/browser/native_window.h"
#include "shell/common/gin_converters/file_path_converter.h"
2018-02-10 02:38:21 +00:00
@interface PopUpButtonHandler : NSObject
2018-04-09 07:51:25 +00:00
@property(nonatomic, assign) NSSavePanel* savePanel;
@property(nonatomic, strong) NSArray* fileTypesList;
- (instancetype)initWithPanel:(NSSavePanel*)panel
andTypesList:(NSArray*)typesList;
2018-02-10 02:38:21 +00:00
- (void)selectFormat:(id)sender;
2018-04-09 07:51:25 +00:00
2018-02-10 02:38:21 +00:00
@end
@implementation PopUpButtonHandler
2018-04-09 07:51:25 +00:00
@synthesize savePanel;
@synthesize fileTypesList;
2018-04-09 07:51:25 +00:00
- (instancetype)initWithPanel:(NSSavePanel*)panel
andTypesList:(NSArray*)typesList {
2018-02-10 02:38:21 +00:00
self = [super init];
if (self) {
2018-04-09 07:51:25 +00:00
[self setSavePanel:panel];
[self setFileTypesList:typesList];
2018-02-10 02:38:21 +00:00
}
return self;
}
- (void)selectFormat:(id)sender {
2018-04-09 07:51:25 +00:00
NSPopUpButton* button = (NSPopUpButton*)sender;
2018-02-10 02:38:21 +00:00
NSInteger selectedItemIndex = [button indexOfSelectedItem];
2018-04-09 07:51:25 +00:00
NSArray* list = [self fileTypesList];
NSArray* fileTypes = [list objectAtIndex:selectedItemIndex];
// If we meet a '*' file extension, we allow all the file types and no
// need to set the specified file types.
2018-04-09 07:51:25 +00:00
if ([fileTypes count] == 0 || [fileTypes containsObject:@"*"])
[[self savePanel] setAllowedFileTypes:nil];
2018-04-09 07:51:25 +00:00
else
[[self savePanel] setAllowedFileTypes:fileTypes];
2018-02-10 02:38:21 +00:00
}
2018-04-09 07:51:25 +00:00
@end
// Manages the PopUpButtonHandler.
@interface ElectronAccessoryView : NSView
2018-04-09 07:51:25 +00:00
@end
@implementation ElectronAccessoryView
2018-04-09 07:51:25 +00:00
- (void)dealloc {
2018-04-20 18:47:04 +00:00
auto* popupButton =
static_cast<NSPopUpButton*>([[self subviews] objectAtIndex:1]);
2018-04-09 07:51:25 +00:00
[[popupButton target] release];
[super dealloc];
}
2018-02-10 02:38:21 +00:00
@end
namespace file_dialog {
DialogSettings::DialogSettings() = default;
DialogSettings::DialogSettings(const DialogSettings&) = default;
DialogSettings::~DialogSettings() = default;
namespace {
2014-08-06 05:47:44 +00:00
void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) {
NSMutableArray* file_types_list = [NSMutableArray array];
NSMutableArray* filter_names = [NSMutableArray array];
// Create array to keep file types and their name.
2018-04-09 07:51:25 +00:00
for (const Filter& filter : filters) {
NSMutableOrderedSet* file_type_set =
[NSMutableOrderedSet orderedSetWithCapacity:filters.size()];
[filter_names addObject:@(filter.first.c_str())];
for (std::string ext : filter.second) {
// macOS is incapable of understanding multiple file extensions,
// so we need to tokenize the extension that's been passed in.
// We want to err on the side of allowing files, so we pass
// along only the final extension ('tar.gz' => 'gz').
auto pos = ext.rfind('.');
if (pos != std::string::npos) {
ext.erase(0, pos + 1);
}
[file_type_set addObject:@(ext.c_str())];
2014-08-06 05:47:44 +00:00
}
[file_types_list addObject:[file_type_set array]];
2014-08-06 05:47:44 +00:00
}
2015-12-31 10:58:16 +00:00
// Passing empty array to setAllowedFileTypes will cause exception.
NSArray* file_types = nil;
NSUInteger count = [file_types_list count];
if (count > 0) {
file_types = [[file_types_list objectAtIndex:0] allObjects];
// If we meet a '*' file extension, we allow all the file types and no
// need to set the specified file types.
2018-04-09 07:51:25 +00:00
if ([file_types count] == 0 || [file_types containsObject:@"*"])
file_types = nil;
}
2015-12-31 10:58:16 +00:00
[dialog setAllowedFileTypes:file_types];
2018-02-09 14:08:04 +00:00
2018-04-09 07:51:25 +00:00
if (count <= 1)
return; // don't add file format picker
2018-02-10 02:38:21 +00:00
2018-04-09 07:51:25 +00:00
// Add file format picker.
ElectronAccessoryView* accessoryView = [[ElectronAccessoryView alloc]
initWithFrame:NSMakeRect(0.0, 0.0, 200, 32.0)];
2018-04-09 07:51:25 +00:00
NSTextField* label =
[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 60, 22)];
2018-02-09 14:08:04 +00:00
[label setEditable:NO];
[label setStringValue:@"Format:"];
[label setBordered:NO];
[label setBezeled:NO];
[label setDrawsBackground:NO];
2018-04-20 18:47:04 +00:00
NSPopUpButton* popupButton =
[[NSPopUpButton alloc] initWithFrame:NSMakeRect(50.0, 2, 140, 22.0)
pullsDown:NO];
PopUpButtonHandler* popUpButtonHandler =
[[PopUpButtonHandler alloc] initWithPanel:dialog
andTypesList:file_types_list];
2018-04-09 07:51:25 +00:00
[popupButton addItemsWithTitles:filter_names];
2018-02-10 02:38:21 +00:00
[popupButton setTarget:popUpButtonHandler];
2018-02-09 14:08:04 +00:00
[popupButton setAction:@selector(selectFormat:)];
2018-04-09 07:51:25 +00:00
[accessoryView addSubview:[label autorelease]];
[accessoryView addSubview:[popupButton autorelease]];
2018-02-09 14:08:04 +00:00
2018-04-09 07:51:25 +00:00
[dialog setAccessoryView:[accessoryView autorelease]];
2014-08-06 05:47:44 +00:00
}
2018-04-20 18:47:04 +00:00
void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
2017-02-08 01:32:58 +00:00
if (!settings.title.empty())
[dialog setTitle:base::SysUTF8ToNSString(settings.title)];
2017-02-08 01:32:58 +00:00
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())
2018-04-20 18:47:04 +00:00
[dialog
setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)];
[dialog setShowsTagField:settings.shows_tag_field];
NSString* default_dir = nil;
NSString* default_filename = nil;
2017-02-08 01:32:58 +00:00
if (!settings.default_path.empty()) {
2018-04-09 07:51:25 +00:00
base::ThreadRestrictions::ScopedAllowIO allow_io;
2017-02-08 01:32:58 +00:00
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 =
2017-02-08 01:32:58 +00:00
base::SysUTF8ToNSString(settings.default_path.BaseName().value());
}
}
2017-06-15 17:27:34 +00:00
if (settings.filters.empty()) {
[dialog setAllowsOtherFileTypes:YES];
2017-06-15 17:27:34 +00:00
} else {
2017-06-03 15:48:18 +00:00
// Set setAllowedFileTypes before setNameFieldStringValue as it might
// override the extension set using setNameFieldStringValue
SetAllowedFileTypes(dialog, settings.filters);
2017-06-03 15:48:18 +00:00
}
// 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 SetupOpenDialogForProperties(NSOpenPanel* dialog, int properties) {
[dialog setCanChooseFiles:(properties & OPEN_DIALOG_OPEN_FILE)];
if (properties & OPEN_DIALOG_OPEN_DIRECTORY)
2013-09-23 11:22:36 +00:00
[dialog setCanChooseDirectories:YES];
if (properties & OPEN_DIALOG_CREATE_DIRECTORY)
2013-09-23 11:22:36 +00:00
[dialog setCanCreateDirectories:YES];
if (properties & OPEN_DIALOG_MULTI_SELECTIONS)
2013-09-23 11:22:36 +00:00
[dialog setAllowsMultipleSelection:YES];
if (properties & OPEN_DIALOG_SHOW_HIDDEN_FILES)
[dialog setShowsHiddenFiles:YES];
if (properties & OPEN_DIALOG_NO_RESOLVE_ALIASES)
[dialog setResolvesAliases:NO];
if (properties & OPEN_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
[dialog setTreatsFilePackagesAsDirectories:YES];
}
void SetupSaveDialogForProperties(NSSavePanel* dialog, int properties) {
if (properties & SAVE_DIALOG_CREATE_DIRECTORY)
[dialog setCanCreateDirectories:YES];
if (properties & SAVE_DIALOG_SHOW_HIDDEN_FILES)
[dialog setShowsHiddenFiles:YES];
if (properties & SAVE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
[dialog setTreatsFilePackagesAsDirectories:YES];
2013-09-23 11:22:36 +00:00
}
// Run modal dialog with parent window and return user's choice.
int RunModalDialog(NSSavePanel* dialog, const DialogSettings& settings) {
__block int chosen = NSFileHandlingPanelCancelButton;
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
settings.force_detached) {
chosen = [dialog runModal];
} else {
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[dialog beginSheetModalForWindow:window
completionHandler:^(NSInteger c) {
2018-04-20 18:47:04 +00:00
chosen = c;
[NSApp stopModal];
}];
[NSApp runModalForWindow:window];
}
return chosen;
}
// Create bookmark data and serialise it into a base64 string.
std::string GetBookmarkDataFromNSURL(NSURL* url) {
// Create the file if it doesn't exist (necessary for NSSavePanel options).
2018-04-20 18:47:04 +00:00
NSFileManager* defaultManager = [NSFileManager defaultManager];
if (![defaultManager fileExistsAtPath:[url path]]) {
[defaultManager createFileAtPath:[url path] contents:nil attributes:nil];
}
2018-04-20 18:47:04 +00:00
NSError* error = nil;
NSData* bookmarkData =
[url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error];
if (error != nil) {
// Send back an empty string if there was an error.
return "";
} else {
// Encode NSData in base64 then convert to NSString.
2018-04-20 18:47:04 +00:00
NSString* base64data = [[NSString alloc]
initWithData:[bookmarkData base64EncodedDataWithOptions:0]
encoding:NSUTF8StringEncoding];
return base::SysNSStringToUTF8(base64data);
}
}
void ReadDialogPathsWithBookmarks(NSOpenPanel* dialog,
std::vector<base::FilePath>* paths,
std::vector<std::string>* bookmarks) {
2013-09-23 11:22:36 +00:00
NSArray* urls = [dialog URLs];
for (NSURL* url in urls)
if ([url isFileURL]) {
2013-09-23 11:22:36 +00:00
paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path])));
bookmarks->push_back(GetBookmarkDataFromNSURL(url));
}
}
void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
std::vector<std::string> ignored_bookmarks;
ReadDialogPathsWithBookmarks(dialog, paths, &ignored_bookmarks);
2013-09-23 11:22:36 +00:00
}
void ResolvePromiseInNextTick(gin_helper::Promise<v8::Local<v8::Value>> promise,
v8::Local<v8::Value> value) {
// The completionHandler runs inside a transaction commit, and we should
// not do any runModal inside it. However since we can not control what
// users will run in the microtask, we have to delay the resolution until
// next tick, otherwise crash like this may happen:
// https://github.com/electron/electron/issues/26884
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(
[](gin_helper::Promise<v8::Local<v8::Value>> promise,
v8::Global<v8::Value> global) {
v8::Isolate* isolate = promise.isolate();
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Value> value = global.Get(isolate);
promise.Resolve(value);
},
std::move(promise), v8::Global<v8::Value>(promise.isolate(), value)));
}
} // namespace
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
DCHECK(paths);
NSOpenPanel* dialog = [NSOpenPanel openPanel];
2017-02-08 01:32:58 +00:00
SetupDialog(dialog, settings);
SetupOpenDialogForProperties(dialog, settings.properties);
int chosen = RunModalDialog(dialog, settings);
if (chosen == NSFileHandlingPanelCancelButton)
return false;
2013-09-23 11:22:36 +00:00
ReadDialogPaths(dialog, paths);
return true;
}
void OpenDialogCompletion(int chosen,
NSOpenPanel* dialog,
bool security_scoped_bookmarks,
gin_helper::Promise<gin_helper::Dictionary> promise) {
v8::HandleScope scope(promise.isolate());
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(promise.isolate());
if (chosen == NSFileHandlingPanelCancelButton) {
dict.Set("canceled", true);
dict.Set("filePaths", std::vector<base::FilePath>());
2018-04-20 18:47:04 +00:00
#if defined(MAS_BUILD)
dict.Set("bookmarks", std::vector<std::string>());
2018-04-20 18:47:04 +00:00
#endif
} else {
std::vector<base::FilePath> paths;
dict.Set("canceled", false);
2018-04-20 18:47:04 +00:00
#if defined(MAS_BUILD)
std::vector<std::string> bookmarks;
if (security_scoped_bookmarks)
2018-04-20 18:47:04 +00:00
ReadDialogPathsWithBookmarks(dialog, &paths, &bookmarks);
else
ReadDialogPaths(dialog, &paths);
dict.Set("filePaths", paths);
dict.Set("bookmarks", bookmarks);
2018-04-20 18:47:04 +00:00
#else
ReadDialogPaths(dialog, &paths);
dict.Set("filePaths", paths);
2018-04-20 18:47:04 +00:00
#endif
}
ResolvePromiseInNextTick(promise.As<v8::Local<v8::Value>>(),
dict.GetHandle());
}
2017-02-08 01:32:58 +00:00
void ShowOpenDialog(const DialogSettings& settings,
gin_helper::Promise<gin_helper::Dictionary> promise) {
2013-09-23 11:22:36 +00:00
NSOpenPanel* dialog = [NSOpenPanel openPanel];
2017-02-08 01:32:58 +00:00
SetupDialog(dialog, settings);
SetupOpenDialogForProperties(dialog, settings.properties);
2013-09-23 11:22:36 +00:00
// Capture the value of the security_scoped_bookmarks settings flag
// and pass it to the completion handler.
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
2013-09-23 11:22:36 +00:00
__block gin_helper::Promise<gin_helper::Dictionary> p = std::move(promise);
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
settings.force_detached) {
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
} else {
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[dialog
beginSheetModalForWindow:window
completionHandler:^(NSInteger chosen) {
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
}
2013-09-23 11:22:36 +00:00
}
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
DCHECK(path);
NSSavePanel* dialog = [NSSavePanel savePanel];
2017-02-08 01:32:58 +00:00
SetupDialog(dialog, settings);
SetupSaveDialogForProperties(dialog, settings.properties);
int chosen = RunModalDialog(dialog, settings);
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
return false;
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
return true;
}
void SaveDialogCompletion(int chosen,
NSSavePanel* dialog,
bool security_scoped_bookmarks,
gin_helper::Promise<gin_helper::Dictionary> promise) {
v8::HandleScope scope(promise.isolate());
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(promise.isolate());
if (chosen == NSFileHandlingPanelCancelButton) {
dict.Set("canceled", true);
dict.Set("filePath", base::FilePath());
2018-04-20 18:47:04 +00:00
#if defined(MAS_BUILD)
dict.Set("bookmark", base::StringPiece());
2018-04-20 18:47:04 +00:00
#endif
} else {
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
dict.Set("filePath", base::FilePath(path));
dict.Set("canceled", false);
2018-04-20 18:47:04 +00:00
#if defined(MAS_BUILD)
std::string bookmark;
if (security_scoped_bookmarks) {
2018-04-20 18:47:04 +00:00
bookmark = GetBookmarkDataFromNSURL([dialog URL]);
dict.Set("bookmark", bookmark);
2018-04-20 18:47:04 +00:00
}
#endif
}
ResolvePromiseInNextTick(promise.As<v8::Local<v8::Value>>(),
dict.GetHandle());
}
2017-02-08 01:32:58 +00:00
void ShowSaveDialog(const DialogSettings& settings,
gin_helper::Promise<gin_helper::Dictionary> promise) {
2013-09-23 12:08:32 +00:00
NSSavePanel* dialog = [NSSavePanel savePanel];
2017-02-08 01:32:58 +00:00
SetupDialog(dialog, settings);
SetupSaveDialogForProperties(dialog, settings.properties);
[dialog setCanSelectHiddenExtension:YES];
2013-09-23 12:08:32 +00:00
// Capture the value of the security_scoped_bookmarks settings flag
// and pass it to the completion handler.
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
2013-09-23 12:08:32 +00:00
__block gin_helper::Promise<gin_helper::Dictionary> p = std::move(promise);
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
settings.force_detached) {
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
} else {
NSWindow* window =
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
[dialog
beginSheetModalForWindow:window
completionHandler:^(NSInteger chosen) {
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
std::move(p));
}];
}
2013-09-23 12:08:32 +00:00
}
} // namespace file_dialog