2014-10-31 18:17:05 +00:00
|
|
|
// 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
|
2013-05-20 13:46:43 +00:00
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2019-06-19 20:46:59 +00:00
|
|
|
#include "shell/browser/ui/file_dialog.h"
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2019-05-02 12:05:37 +00:00
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
#import <Cocoa/Cocoa.h>
|
2014-03-16 01:37:04 +00:00
|
|
|
#import <CoreServices/CoreServices.h>
|
2013-05-20 13:46:43 +00:00
|
|
|
|
chore: bump chromium to 118.0.5975.0 (main) (#39531)
* chore: bump chromium in DEPS to 118.0.5951.0
* chore: update printing.patch
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/4727894
No logic changes, but patch needed to be manually re-applied due to upstream code shear
* chore: update port_autofill_colors_to_the_color_pipeline.patch
No manual changes; patch applied with fuzz
* chore: update patches
* chore: bump chromium in DEPS to 118.0.5953.0
* chore: update patches
* chore: bump chromium in DEPS to 118.0.5955.0
* chore: update patches
* chore: bump chromium in DEPS to 118.0.5957.0
* chore: update patches
* chore: include path of native_web_keyboard_event.h
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/4758689
* chore: remove reference to eextensions/browser/notification-types.h
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/4771627
* chore: update references to renamed upstream field NativeWebKeyboardEvent.skip_if_unhandled (formerly known as skip_in_browser
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/4758689
Need a second pair of eyes on this commit. In particular the reference in content_converter.cc, skipInBrowser, seems to not be set or documented anywhere? Is this unused/vestigal code?
* chore: sync signature of ElectronExtensionsBrowserClient::IsValidContext() to upstream change
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/4784198
* chore: add auto_pip_setting_helper.[cc,h] to chromium_src build
Xref: https://chromium-review.googlesource.com/c/chromium/src/+/4688277
Exiting upstream code used by chromium_src now depends on this new upstream class
* chore: bump chromium in DEPS to 118.0.5959.0
* chore: update add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
Xref: add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
manually adjust patch to minor upstream chagnes
* chore: update patches
* chore: bump chromium in DEPS to 118.0.5961.0
* chore: bump chromium in DEPS to 118.0.5963.0
* chore: update patches
* 4780994: Rename various base files to "apple" since iOS uses them too
https://chromium-review.googlesource.com/c/chromium/src/+/4780994
* Many files moved from `mac` -> `apple`
This commit follows a handful of CLs that simply rename files/symbols to change `mac`
to `apple`
to signify their use across both macOS and iOS:
- 4784010: Move scoped_nsautorelease_pool to base/apple, leave a forwarding header
- 4790744: Move foundation_util to base/apple, leave a forwarding header
- 4790741: Move scoped_cftypreref to base/apple, leave a forwarding header
- 4787627: Move and rename macOS+iOS base/ files in PA to "apple"
- 4780399: Move OSStatus logging to base/apple
- 4787387: Remove forwarding headers
- 4781113: Rename message_pump_mac to "apple" because iOS uses it too
* fixup minor patch update error
A function param got dropped from this patch somewhere earlier
* chore: bump chromium in DEPS to 118.0.5965.2
* chore: update patches
* 4799213: Move ScopedTypeRef and ScopedCFTypeRef into base::apple::
https://chromium-review.googlesource.com/c/chromium/src/+/4799213
* Fix removed include to BrowserContext
In crrev.com/c/4767962 an include to BrowserContext was removed,
which was necessary for compilation. This broke only for us because
"chrome/browser/profiles/profile.h" includes that class, but we remove
all references to profiles.
* chore: bump chromium in DEPS to 118.0.5967.0
* chore: update patches
* chore: bump chromium in DEPS to 118.0.5969.0
* chore: update patches
* chore: bump chromium in DEPS to 118.0.5971.0
* chore: bump chromium in DEPS to 118.0.5973.0
* chore: update patches
* 4772121: [OOPIF PDF] Replace PDFWebContentsHelper with PDFDocumentHelper
https://chromium-review.googlesource.com/c/chromium/src/+/4772121
* 4811164: [Extensions] Do some cleanup in ChromeManagementAPIDelegate.
https://chromium-review.googlesource.com/c/chromium/src/+/4811164
* 4809488: Remove duplicate dnd functionality between Web and Renderer prefs
https://chromium-review.googlesource.com/c/chromium/src/+/4809488
Given that this is no longer an option of web preferences, we should
consider deprecating this option and then removing it.
* chore: bump chromium in DEPS to 118.0.5975.0
* chore: update patches
* fixup! chore: add auto_pip_settings_helper.{cc|h} to chromium_src build
* Reland "[windows] Remove RegKey::DeleteEmptyKey"
Refs https://chromium-review.googlesource.com/c/chromium/src/+/4813255
* Ensure StrCat means StrCat
Refs https://chromium-review.googlesource.com/c/chromium/src/+/1117180
* fixup! Remove RegKey::DeleteEmptyKey
* Consistently reject large p and large q in DH
Refs https://boringssl-review.googlesource.com/c/boringssl/+/62226
---------
Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: clavin <clavin@electronjs.org>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2023-09-01 06:54:59 +00:00
|
|
|
#include "base/apple/foundation_util.h"
|
|
|
|
#include "base/apple/scoped_cftyperef.h"
|
2015-01-10 01:45:50 +00:00
|
|
|
#include "base/files/file_util.h"
|
2014-08-06 05:47:44 +00:00
|
|
|
#include "base/mac/mac_util.h"
|
2013-05-20 13:46:43 +00:00
|
|
|
#include "base/strings/sys_string_conversions.h"
|
2021-11-01 21:08:31 +00:00
|
|
|
#include "content/public/browser/browser_task_traits.h"
|
|
|
|
#include "content/public/browser/browser_thread.h"
|
2019-06-19 20:46:59 +00:00
|
|
|
#include "shell/browser/native_window.h"
|
2019-09-06 05:52:54 +00:00
|
|
|
#include "shell/common/gin_converters/file_path_converter.h"
|
2022-11-17 19:59:23 +00:00
|
|
|
#include "shell/common/thread_restrictions.h"
|
2013-05-20 13:46:43 +00:00
|
|
|
|
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
|
|
|
|
2018-04-17 23:46:33 +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];
|
2018-02-19 13:40:07 +00:00
|
|
|
|
|
|
|
// 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:@"*"])
|
2018-02-19 13:40:07 +00:00
|
|
|
[[self savePanel] setAllowedFileTypes:nil];
|
2018-04-09 07:51:25 +00:00
|
|
|
else
|
2018-02-19 13:40:07 +00:00
|
|
|
[[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
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation ElectronAccessoryView
|
|
|
|
|
|
|
|
- (void)dealloc {
|
2018-04-20 18:47:04 +00:00
|
|
|
auto* popupButton =
|
|
|
|
static_cast<NSPopUpButton*>([[self subviews] objectAtIndex:1]);
|
2023-08-04 08:47:29 +00:00
|
|
|
popupButton.target = nil;
|
2018-04-09 07:51:25 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 02:38:21 +00:00
|
|
|
@end
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
namespace file_dialog {
|
|
|
|
|
2018-04-17 23:37:22 +00:00
|
|
|
DialogSettings::DialogSettings() = default;
|
2018-11-08 14:51:06 +00:00
|
|
|
DialogSettings::DialogSettings(const DialogSettings&) = default;
|
2018-04-17 23:37:22 +00:00
|
|
|
DialogSettings::~DialogSettings() = default;
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
namespace {
|
|
|
|
|
2014-08-06 05:47:44 +00:00
|
|
|
void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) {
|
2018-02-19 13:40:07 +00:00
|
|
|
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) {
|
2020-08-28 16:40:47 +00:00
|
|
|
NSMutableOrderedSet* file_type_set =
|
|
|
|
[NSMutableOrderedSet orderedSetWithCapacity:filters.size()];
|
2018-06-18 07:32:55 +00:00
|
|
|
[filter_names addObject:@(filter.first.c_str())];
|
2020-05-07 15:52:56 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-06-18 07:32:55 +00:00
|
|
|
[file_type_set addObject:@(ext.c_str())];
|
2014-08-06 05:47:44 +00:00
|
|
|
}
|
2020-08-28 16:40:47 +00:00
|
|
|
[file_types_list addObject:[file_type_set array]];
|
2014-08-06 05:47:44 +00:00
|
|
|
}
|
2015-12-30 20:38:02 +00:00
|
|
|
|
2015-12-31 10:58:16 +00:00
|
|
|
// Passing empty array to setAllowedFileTypes will cause exception.
|
2015-12-30 20:38:02 +00:00
|
|
|
NSArray* file_types = nil;
|
2018-02-19 13:40:07 +00:00
|
|
|
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:@"*"])
|
2018-02-19 13:40:07 +00:00
|
|
|
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)];
|
|
|
|
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:)];
|
|
|
|
|
2023-08-04 08:47:29 +00:00
|
|
|
[accessoryView addSubview:label];
|
|
|
|
[accessoryView addSubview:popupButton];
|
2018-02-09 14:08:04 +00:00
|
|
|
|
2023-08-04 08:47:29 +00:00
|
|
|
[dialog setAccessoryView:accessoryView];
|
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)];
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
if (!settings.button_label.empty())
|
|
|
|
[dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)];
|
2016-05-06 18:10:31 +00:00
|
|
|
|
2017-02-09 19:25:05 +00:00
|
|
|
if (!settings.message.empty())
|
|
|
|
[dialog setMessage:base::SysUTF8ToNSString(settings.message)];
|
2017-02-01 15:34:21 +00:00
|
|
|
|
2017-02-09 19:25:05 +00:00
|
|
|
if (!settings.name_field_label.empty())
|
2018-04-20 18:47:04 +00:00
|
|
|
[dialog
|
|
|
|
setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)];
|
2017-02-01 15:34:21 +00:00
|
|
|
|
2017-02-09 19:25:05 +00:00
|
|
|
[dialog setShowsTagField:settings.shows_tag_field];
|
2017-02-01 15:34:21 +00:00
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
NSString* default_dir = nil;
|
|
|
|
NSString* default_filename = nil;
|
2017-02-08 01:32:58 +00:00
|
|
|
if (!settings.default_path.empty()) {
|
2022-11-17 19:59:23 +00:00
|
|
|
electron::ScopedAllowBlockingForElectron allow_blocking;
|
2017-02-08 01:32:58 +00:00
|
|
|
if (base::DirectoryExists(settings.default_path)) {
|
|
|
|
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
|
2013-05-20 13:46:43 +00:00
|
|
|
} else {
|
2017-05-25 23:11:58 +00:00
|
|
|
if (settings.default_path.IsAbsolute()) {
|
|
|
|
default_dir =
|
|
|
|
base::SysUTF8ToNSString(settings.default_path.DirName().value());
|
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
default_filename =
|
2017-02-08 01:32:58 +00:00
|
|
|
base::SysUTF8ToNSString(settings.default_path.BaseName().value());
|
2013-05-20 13:46:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-15 17:27:34 +00:00
|
|
|
if (settings.filters.empty()) {
|
2017-05-28 19:20:22 +00:00
|
|
|
[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
|
2017-05-28 19:20:22 +00:00
|
|
|
SetAllowedFileTypes(dialog, settings.filters);
|
2017-06-03 15:48:18 +00:00
|
|
|
}
|
2017-05-28 19:20:22 +00:00
|
|
|
|
2017-09-07 23:43:37 +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];
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
if (default_dir)
|
|
|
|
[dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
|
|
|
|
if (default_filename)
|
|
|
|
[dialog setNameFieldStringValue:default_filename];
|
|
|
|
}
|
|
|
|
|
2019-08-13 20:40:07 +00:00
|
|
|
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];
|
2019-08-13 20:40:07 +00:00
|
|
|
if (properties & OPEN_DIALOG_CREATE_DIRECTORY)
|
2013-09-23 11:22:36 +00:00
|
|
|
[dialog setCanCreateDirectories:YES];
|
2019-08-13 20:40:07 +00:00
|
|
|
if (properties & OPEN_DIALOG_MULTI_SELECTIONS)
|
2013-09-23 11:22:36 +00:00
|
|
|
[dialog setAllowsMultipleSelection:YES];
|
2019-08-13 20:40:07 +00:00
|
|
|
if (properties & OPEN_DIALOG_SHOW_HIDDEN_FILES)
|
2016-07-11 04:30:18 +00:00
|
|
|
[dialog setShowsHiddenFiles:YES];
|
2019-08-13 20:40:07 +00:00
|
|
|
if (properties & OPEN_DIALOG_NO_RESOLVE_ALIASES)
|
2017-02-13 16:23:49 +00:00
|
|
|
[dialog setResolvesAliases:NO];
|
2019-08-13 20:40:07 +00:00
|
|
|
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)
|
2017-07-18 01:40:57 +00:00
|
|
|
[dialog setTreatsFilePackagesAsDirectories:YES];
|
2013-09-23 11:22:36 +00:00
|
|
|
}
|
|
|
|
|
2013-09-23 11:42:07 +00:00
|
|
|
// Run modal dialog with parent window and return user's choice.
|
2017-11-02 21:50:04 +00:00
|
|
|
int RunModalDialog(NSSavePanel* dialog, const DialogSettings& settings) {
|
2022-06-01 06:12:47 +00:00
|
|
|
__block int chosen = NSModalResponseCancel;
|
2017-11-02 21:50:04 +00:00
|
|
|
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
|
|
|
settings.force_detached) {
|
2013-09-23 11:42:07 +00:00
|
|
|
chosen = [dialog runModal];
|
|
|
|
} else {
|
2019-01-09 20:25:19 +00:00
|
|
|
NSWindow* window =
|
|
|
|
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
2013-09-23 11:42:07 +00:00
|
|
|
|
|
|
|
[dialog beginSheetModalForWindow:window
|
|
|
|
completionHandler:^(NSInteger c) {
|
2018-04-20 18:47:04 +00:00
|
|
|
chosen = c;
|
|
|
|
[NSApp stopModal];
|
|
|
|
}];
|
2013-09-23 11:42:07 +00:00
|
|
|
[NSApp runModalForWindow:window];
|
|
|
|
}
|
|
|
|
|
|
|
|
return chosen;
|
|
|
|
}
|
|
|
|
|
2018-02-12 18:25:06 +00:00
|
|
|
// 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-02-12 18:25:06 +00:00
|
|
|
}
|
|
|
|
|
2018-04-20 18:47:04 +00:00
|
|
|
NSError* error = nil;
|
|
|
|
NSData* bookmarkData =
|
|
|
|
[url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
|
|
|
|
includingResourceValuesForKeys:nil
|
|
|
|
relativeToURL:nil
|
|
|
|
error:&error];
|
2018-02-12 18:25:06 +00:00
|
|
|
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];
|
2018-02-12 18:25:06 +00:00
|
|
|
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];
|
2023-06-05 12:08:10 +00:00
|
|
|
for (NSURL* url in urls) {
|
|
|
|
if (![url isFileURL])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
NSString* path = [url path];
|
|
|
|
|
|
|
|
// There's a bug in macOS where despite a request to disallow file
|
|
|
|
// selection, files/packages can be selected. If file selection
|
|
|
|
// was disallowed, drop any files selected. See crbug.com/1357523.
|
|
|
|
if (![dialog canChooseFiles]) {
|
|
|
|
BOOL is_directory;
|
|
|
|
BOOL exists =
|
|
|
|
[[NSFileManager defaultManager] fileExistsAtPath:path
|
|
|
|
isDirectory:&is_directory];
|
|
|
|
BOOL is_package =
|
|
|
|
[[NSWorkspace sharedWorkspace] isFilePackageAtPath:path];
|
|
|
|
if (!exists || !is_directory || is_package)
|
|
|
|
continue;
|
2018-02-12 18:25:06 +00:00
|
|
|
}
|
2023-06-05 12:08:10 +00:00
|
|
|
|
|
|
|
paths->emplace_back(base::SysNSStringToUTF8(path));
|
|
|
|
bookmarks->push_back(GetBookmarkDataFromNSURL(url));
|
|
|
|
}
|
2018-02-12 18:25:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-11-01 21:08:31 +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
|
2022-05-17 16:48:40 +00:00
|
|
|
content::GetUIThreadTaskRunner({})->PostTask(
|
|
|
|
FROM_HERE,
|
2021-11-01 21:08:31 +00:00
|
|
|
base::BindOnce(
|
|
|
|
[](gin_helper::Promise<v8::Local<v8::Value>> promise,
|
|
|
|
v8::Global<v8::Value> global) {
|
|
|
|
v8::Isolate* isolate = promise.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)));
|
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
} // namespace
|
|
|
|
|
2019-03-05 13:54:48 +00:00
|
|
|
bool ShowOpenDialogSync(const DialogSettings& settings,
|
|
|
|
std::vector<base::FilePath>* paths) {
|
2013-05-20 13:46:43 +00:00
|
|
|
DCHECK(paths);
|
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
SetupDialog(dialog, settings);
|
2019-08-13 20:40:07 +00:00
|
|
|
SetupOpenDialogForProperties(dialog, settings.properties);
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2017-11-02 21:50:04 +00:00
|
|
|
int chosen = RunModalDialog(dialog, settings);
|
2022-06-01 06:12:47 +00:00
|
|
|
if (chosen == NSModalResponseCancel)
|
2013-05-20 13:46:43 +00:00
|
|
|
return false;
|
|
|
|
|
2013-09-23 11:22:36 +00:00
|
|
|
ReadDialogPaths(dialog, paths);
|
2013-05-20 13:46:43 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-11-01 06:10:32 +00:00
|
|
|
void OpenDialogCompletion(int chosen,
|
|
|
|
NSOpenPanel* dialog,
|
|
|
|
bool security_scoped_bookmarks,
|
|
|
|
gin_helper::Promise<gin_helper::Dictionary> promise) {
|
2020-03-11 01:16:58 +00:00
|
|
|
v8::HandleScope scope(promise.isolate());
|
2019-09-08 15:10:18 +00:00
|
|
|
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(promise.isolate());
|
2022-06-01 06:12:47 +00:00
|
|
|
if (chosen == NSModalResponseCancel) {
|
2019-03-05 13:54:48 +00:00
|
|
|
dict.Set("canceled", true);
|
|
|
|
dict.Set("filePaths", std::vector<base::FilePath>());
|
2022-11-14 20:46:52 +00:00
|
|
|
#if IS_MAS_BUILD()
|
2019-03-05 13:54:48 +00:00
|
|
|
dict.Set("bookmarks", std::vector<std::string>());
|
2018-04-20 18:47:04 +00:00
|
|
|
#endif
|
2018-02-12 18:25:06 +00:00
|
|
|
} else {
|
|
|
|
std::vector<base::FilePath> paths;
|
2019-03-05 13:54:48 +00:00
|
|
|
dict.Set("canceled", false);
|
2022-11-14 20:46:52 +00:00
|
|
|
#if IS_MAS_BUILD()
|
2018-04-20 18:47:04 +00:00
|
|
|
std::vector<std::string> bookmarks;
|
2019-03-05 13:54:48 +00:00
|
|
|
if (security_scoped_bookmarks)
|
2018-04-20 18:47:04 +00:00
|
|
|
ReadDialogPathsWithBookmarks(dialog, &paths, &bookmarks);
|
2019-03-05 13:54:48 +00:00
|
|
|
else
|
2018-02-12 18:25:06 +00:00
|
|
|
ReadDialogPaths(dialog, &paths);
|
2019-03-05 13:54:48 +00:00
|
|
|
dict.Set("filePaths", paths);
|
|
|
|
dict.Set("bookmarks", bookmarks);
|
2018-04-20 18:47:04 +00:00
|
|
|
#else
|
|
|
|
ReadDialogPaths(dialog, &paths);
|
2019-03-05 13:54:48 +00:00
|
|
|
dict.Set("filePaths", paths);
|
2018-04-20 18:47:04 +00:00
|
|
|
#endif
|
2018-02-12 18:25:06 +00:00
|
|
|
}
|
2021-11-01 21:08:31 +00:00
|
|
|
ResolvePromiseInNextTick(promise.As<v8::Local<v8::Value>>(),
|
|
|
|
dict.GetHandle());
|
2018-02-12 18:25:06 +00:00
|
|
|
}
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
void ShowOpenDialog(const DialogSettings& settings,
|
2019-11-01 06:10:32 +00:00
|
|
|
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);
|
2019-08-13 20:40:07 +00:00
|
|
|
SetupOpenDialogForProperties(dialog, settings.properties);
|
2013-09-23 11:22:36 +00:00
|
|
|
|
2019-02-27 08:14:04 +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
|
|
|
|
2019-11-01 06:10:32 +00:00
|
|
|
__block gin_helper::Promise<gin_helper::Dictionary> p = std::move(promise);
|
2019-03-05 13:54:48 +00:00
|
|
|
|
2017-09-13 07:36:28 +00:00
|
|
|
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
2017-11-02 21:50:04 +00:00
|
|
|
settings.force_detached) {
|
2018-03-22 12:57:33 +00:00
|
|
|
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
|
2019-03-05 13:54:48 +00:00
|
|
|
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
|
|
|
std::move(p));
|
2018-03-22 12:57:33 +00:00
|
|
|
}];
|
2017-09-13 07:36:28 +00:00
|
|
|
} else {
|
2019-01-09 20:25:19 +00:00
|
|
|
NSWindow* window =
|
|
|
|
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
2019-03-05 13:54:48 +00:00
|
|
|
[dialog
|
|
|
|
beginSheetModalForWindow:window
|
|
|
|
completionHandler:^(NSInteger chosen) {
|
|
|
|
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
|
|
|
std::move(p));
|
|
|
|
}];
|
2017-09-13 07:36:28 +00:00
|
|
|
}
|
2013-09-23 11:22:36 +00:00
|
|
|
}
|
|
|
|
|
2019-03-05 21:48:20 +00:00
|
|
|
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
|
2013-05-20 13:46:43 +00:00
|
|
|
DCHECK(path);
|
|
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
SetupDialog(dialog, settings);
|
2019-08-13 20:40:07 +00:00
|
|
|
SetupSaveDialogForProperties(dialog, settings.properties);
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2017-11-02 21:50:04 +00:00
|
|
|
int chosen = RunModalDialog(dialog, settings);
|
2022-06-01 06:12:47 +00:00
|
|
|
if (chosen == NSModalResponseCancel || ![[dialog URL] isFileURL])
|
2013-09-23 11:36:52 +00:00
|
|
|
return false;
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2013-09-23 11:36:52 +00:00
|
|
|
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
|
|
|
|
return true;
|
2013-05-20 13:46:43 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 06:10:32 +00:00
|
|
|
void SaveDialogCompletion(int chosen,
|
|
|
|
NSSavePanel* dialog,
|
|
|
|
bool security_scoped_bookmarks,
|
|
|
|
gin_helper::Promise<gin_helper::Dictionary> promise) {
|
2020-03-11 01:16:58 +00:00
|
|
|
v8::HandleScope scope(promise.isolate());
|
2019-09-08 15:10:18 +00:00
|
|
|
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(promise.isolate());
|
2022-06-01 06:12:47 +00:00
|
|
|
if (chosen == NSModalResponseCancel) {
|
2019-03-05 21:48:20 +00:00
|
|
|
dict.Set("canceled", true);
|
|
|
|
dict.Set("filePath", base::FilePath());
|
2022-11-14 20:46:52 +00:00
|
|
|
#if IS_MAS_BUILD()
|
2019-09-06 05:52:54 +00:00
|
|
|
dict.Set("bookmark", base::StringPiece());
|
2018-04-20 18:47:04 +00:00
|
|
|
#endif
|
2018-02-12 18:25:06 +00:00
|
|
|
} else {
|
|
|
|
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
2019-03-05 21:48:20 +00:00
|
|
|
dict.Set("filePath", base::FilePath(path));
|
|
|
|
dict.Set("canceled", false);
|
2022-11-14 20:46:52 +00:00
|
|
|
#if IS_MAS_BUILD()
|
2018-04-20 18:47:04 +00:00
|
|
|
std::string bookmark;
|
2019-02-27 08:14:04 +00:00
|
|
|
if (security_scoped_bookmarks) {
|
2018-04-20 18:47:04 +00:00
|
|
|
bookmark = GetBookmarkDataFromNSURL([dialog URL]);
|
2019-03-05 21:48:20 +00:00
|
|
|
dict.Set("bookmark", bookmark);
|
2018-04-20 18:47:04 +00:00
|
|
|
}
|
|
|
|
#endif
|
2018-02-12 18:25:06 +00:00
|
|
|
}
|
2021-11-01 21:08:31 +00:00
|
|
|
ResolvePromiseInNextTick(promise.As<v8::Local<v8::Value>>(),
|
|
|
|
dict.GetHandle());
|
2018-02-12 18:25:06 +00:00
|
|
|
}
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
void ShowSaveDialog(const DialogSettings& settings,
|
2019-11-01 06:10:32 +00:00
|
|
|
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);
|
2019-08-13 20:40:07 +00:00
|
|
|
SetupSaveDialogForProperties(dialog, settings.properties);
|
2016-08-16 23:08:01 +00:00
|
|
|
[dialog setCanSelectHiddenExtension:YES];
|
2013-09-23 12:08:32 +00:00
|
|
|
|
2019-03-05 21:48:20 +00:00
|
|
|
// Capture the value of the security_scoped_bookmarks settings flag
|
|
|
|
// and pass it to the completion handler.
|
2019-02-27 08:14:04 +00:00
|
|
|
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
|
2013-09-23 12:08:32 +00:00
|
|
|
|
2019-11-01 06:10:32 +00:00
|
|
|
__block gin_helper::Promise<gin_helper::Dictionary> p = std::move(promise);
|
2019-03-05 21:48:20 +00:00
|
|
|
|
2017-09-13 07:36:28 +00:00
|
|
|
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
2017-11-02 21:50:04 +00:00
|
|
|
settings.force_detached) {
|
2018-03-22 12:57:33 +00:00
|
|
|
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
|
2019-03-05 21:48:20 +00:00
|
|
|
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
|
|
|
std::move(p));
|
2018-03-22 12:57:33 +00:00
|
|
|
}];
|
2017-09-13 07:36:28 +00:00
|
|
|
} else {
|
2019-01-09 20:25:19 +00:00
|
|
|
NSWindow* window =
|
|
|
|
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
2019-03-05 21:48:20 +00:00
|
|
|
[dialog
|
|
|
|
beginSheetModalForWindow:window
|
|
|
|
completionHandler:^(NSInteger chosen) {
|
|
|
|
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
|
|
|
std::move(p));
|
|
|
|
}];
|
2017-09-13 07:36:28 +00:00
|
|
|
}
|
2013-09-23 12:08:32 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
} // namespace file_dialog
|