diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 2958b7273b..197acc3a2e 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -68,18 +68,21 @@ v8::Local ShowOpenDialog( return handle; } -void ShowSaveDialog(const file_dialog::DialogSettings& settings, - mate::Arguments* args) { - v8::Local peek = args->PeekNext(); - file_dialog::SaveDialogCallback callback; - if (mate::Converter::FromV8( - args->isolate(), peek, &callback)) { - file_dialog::ShowSaveDialog(settings, callback); - } else { - base::FilePath path; - if (file_dialog::ShowSaveDialog(settings, &path)) - args->Return(path); - } +void ShowSaveDialogSync(const file_dialog::DialogSettings& settings, + mate::Arguments* args) { + base::FilePath path; + if (file_dialog::ShowSaveDialogSync(settings, &path)) + args->Return(path); +} + +v8::Local ShowSaveDialog( + const file_dialog::DialogSettings& settings, + mate::Arguments* args) { + atom::util::Promise promise(args->isolate()); + v8::Local handle = promise.GetHandle(); + + file_dialog::ShowSaveDialog(settings, std::move(promise)); + return handle; } void Initialize(v8::Local exports, @@ -91,6 +94,7 @@ void Initialize(v8::Local exports, dict.SetMethod("showErrorBox", &atom::ShowErrorBox); dict.SetMethod("showOpenDialogSync", &ShowOpenDialogSync); dict.SetMethod("showOpenDialog", &ShowOpenDialog); + dict.SetMethod("showSaveDialogSync", &ShowSaveDialogSync); dict.SetMethod("showSaveDialog", &ShowSaveDialog); #if defined(OS_MACOSX) || defined(OS_WIN) dict.SetMethod("showCertificateTrustDialog", diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 49851db4ff..7fe2292abb 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -5,12 +5,14 @@ #include "atom/browser/atom_download_manager_delegate.h" #include +#include #include "atom/browser/api/atom_api_download_item.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "atom/browser/web_contents_preferences.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/options_switches.h" #include "base/bind.h" #include "base/files/file_util.h" @@ -119,10 +121,14 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( !web_preferences || web_preferences->IsEnabled(options::kOffscreen); settings.force_detached = offscreen; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + atom::util::Promise dialog_promise(isolate); auto dialog_callback = base::Bind(&AtomDownloadManagerDelegate::OnDownloadSaveDialogDone, base::Unretained(this), download_id, callback); - file_dialog::ShowSaveDialog(settings, dialog_callback); + + file_dialog::ShowSaveDialog(settings, std::move(dialog_promise)); + ignore_result(dialog_promise.Then(dialog_callback)); } else { callback.Run(path, download::DownloadItem::TARGET_DISPOSITION_PROMPT, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path, diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index e5e6e196ba..dae1ec367d 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -382,7 +382,7 @@ void CommonWebContentsDelegate::DevToolsSaveToFile(const std::string& url, settings.force_detached = offscreen_; settings.title = url; settings.default_path = base::FilePath::FromUTF8Unsafe(url); - if (!file_dialog::ShowSaveDialog(settings, &path)) { + if (!file_dialog::ShowSaveDialogSync(settings, &path)) { base::Value url_value(url); web_contents_->CallClientFunction("DevToolsAPI.canceledSaveURL", &url_value, nullptr, nullptr); diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index 6666b369c8..ba50bd29bc 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -36,16 +36,6 @@ enum FileDialogProperty { FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 7, }; -#if defined(MAS_BUILD) -typedef base::Callback - SaveDialogCallback; -#else -typedef base::Callback - SaveDialogCallback; -#endif - struct DialogSettings { atom::NativeWindow* parent_window = nullptr; std::string title; @@ -70,10 +60,10 @@ bool ShowOpenDialogSync(const DialogSettings& settings, void ShowOpenDialog(const DialogSettings& settings, atom::util::Promise promise); -bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path); +bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path); void ShowSaveDialog(const DialogSettings& settings, - const SaveDialogCallback& callback); + atom::util::Promise promise); } // namespace file_dialog diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 75447c46e6..3c245e5521 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -126,8 +126,8 @@ class FileChooserDialog { gtk_window_present_with_time(GTK_WINDOW(dialog_), time); } - void RunSaveAsynchronous(const SaveDialogCallback& callback) { - save_callback_ = callback; + void RunSaveAsynchronous(atom::util::Promise promise) { + save_promise_.reset(new atom::util::Promise(std::move(promise))); RunAsynchronous(); } @@ -173,7 +173,7 @@ class FileChooserDialog { GtkWidget* preview_; Filters filters_; - SaveDialogCallback save_callback_; + std::unique_ptr save_promise_; std::unique_ptr open_promise_; // Callback for when we update the preview for the selection. @@ -184,12 +184,17 @@ class FileChooserDialog { void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) { gtk_widget_hide(dialog_); - - if (!save_callback_.is_null()) { - if (response == GTK_RESPONSE_ACCEPT) - save_callback_.Run(true, GetFileName()); - else - save_callback_.Run(false, base::FilePath()); + if (save_promise_) { + mate::Dictionary dict = + mate::Dictionary::CreateEmpty(save_promise_->isolate()); + if (response == GTK_RESPONSE_ACCEPT) { + dict.Set("canceled", false); + dict.Set("filePath", GetFileName()); + } else { + dict.Set("canceled", true); + dict.Set("filePath", base::FilePath()); + } + save_promise_->Resolve(dict.GetHandle()); } else if (open_promise_) { mate::Dictionary dict = mate::Dictionary::CreateEmpty(open_promise_->isolate()); @@ -285,23 +290,22 @@ void ShowOpenDialog(const DialogSettings& settings, open_dialog->RunOpenAsynchronous(std::move(promise)); } -bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { +bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) { FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings); gtk_widget_show_all(save_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog())); if (response == GTK_RESPONSE_ACCEPT) { *path = save_dialog.GetFileName(); return true; - } else { - return false; } + return false; } void ShowSaveDialog(const DialogSettings& settings, - const SaveDialogCallback& callback) { + atom::util::Promise promise) { FileChooserDialog* save_dialog = new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings); - save_dialog->RunSaveAsynchronous(callback); + save_dialog->RunSaveAsynchronous(std::move(promise)); } } // namespace file_dialog diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index fb0b61368f..d6aaecb93e 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -346,7 +346,7 @@ void ShowOpenDialog(const DialogSettings& settings, } } -bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { +bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) { DCHECK(path); NSSavePanel* dialog = [NSSavePanel savePanel]; @@ -363,50 +363,57 @@ bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { void SaveDialogCompletion(int chosen, NSSavePanel* dialog, bool security_scoped_bookmarks, - const SaveDialogCallback& callback) { + atom::util::Promise promise) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate()); if (chosen == NSFileHandlingPanelCancelButton) { + dict.Set("canceled", true); + dict.Set("filePath", base::FilePath()); #if defined(MAS_BUILD) - callback.Run(false, base::FilePath(), ""); -#else - callback.Run(false, base::FilePath()); + dict.Set("bookmark", ""); #endif } else { std::string path = base::SysNSStringToUTF8([[dialog URL] path]); + dict.Set("filePath", base::FilePath(path)); + dict.Set("canceled", false); #if defined(MAS_BUILD) std::string bookmark; if (security_scoped_bookmarks) { bookmark = GetBookmarkDataFromNSURL([dialog URL]); + dict.Set("bookmark", bookmark); } - callback.Run(true, base::FilePath(path), bookmark); -#else - callback.Run(true, base::FilePath(path)); #endif } + promise.Resolve(dict.GetHandle()); } void ShowSaveDialog(const DialogSettings& settings, - const SaveDialogCallback& c) { + atom::util::Promise promise) { NSSavePanel* dialog = [NSSavePanel savePanel]; SetupDialog(dialog, settings); [dialog setCanSelectHiddenExtension:YES]; - __block SaveDialogCallback callback = c; + // 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; + __block atom::util::Promise 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, callback); + 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, callback); - }]; + [dialog + beginSheetModalForWindow:window + completionHandler:^(NSInteger chosen) { + SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks, + std::move(p)); + }]; } } diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 46e9191ab8..c8d7f36c06 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -101,13 +101,23 @@ void RunOpenDialogInNewThread(const RunState& run_state, run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); } +void OnSaveDialogDone(atom::util::Promise promise, + bool canceled, + const base::FilePath path) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate()); + dict.Set("canceled", canceled); + dict.Set("filePath", path); + promise.Resolve(dict.GetHandle()); +} + void RunSaveDialogInNewThread(const RunState& run_state, const DialogSettings& settings, - const SaveDialogCallback& callback) { + atom::util::Promise promise) { base::FilePath path; - bool result = ShowSaveDialog(settings, &path); - run_state.ui_task_runner->PostTask(FROM_HERE, - base::Bind(callback, result, path)); + bool result = ShowSaveDialogSync(settings, &path); + run_state.ui_task_runner->PostTask( + FROM_HERE, + base::BindOnce(&OnSaveDialogDone, std::move(promise), result, path)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); } @@ -275,7 +285,7 @@ void ShowOpenDialog(const DialogSettings& settings, } } -bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { +bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) { ATL::CComPtr file_save_dialog; HRESULT hr = file_save_dialog.CoCreateInstance(CLSID_FileSaveDialog); if (FAILED(hr)) @@ -306,16 +316,18 @@ bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { } void ShowSaveDialog(const DialogSettings& settings, - const SaveDialogCallback& callback) { + atom::util::Promise promise) { RunState run_state; if (!CreateDialogThread(&run_state)) { - callback.Run(false, base::FilePath()); - return; + mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate()); + dict.Set("canceled", false); + dict.Set("filePath", base::FilePath()); + promise.Resolve(dict.GetHandle()); + } else { + run_state.dialog_thread->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&RunSaveDialogInNewThread, run_state, + settings, std::move(promise))); } - - run_state.dialog_thread->task_runner()->PostTask( - FROM_HERE, - base::Bind(&RunSaveDialogInNewThread, run_state, settings, callback)); } } // namespace file_dialog diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index b68220b5be..1284808c73 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -56,8 +56,16 @@ class FileSelectHelper : public base::RefCounted, } void ShowSaveDialog(const file_dialog::DialogSettings& settings) { - auto callback = base::Bind(&FileSelectHelper::OnSaveDialogDone, this); - file_dialog::ShowSaveDialog(settings, callback); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local context = isolate->GetCurrentContext(); + atom::util::Promise promise(isolate); + v8::Local handle = promise.GetHandle(); + + file_dialog::ShowSaveDialog(settings, std::move(promise)); + ignore_result(handle->Then( + context, + v8::Local::Cast(mate::ConvertToV8( + isolate, base::Bind(&FileSelectHelper::OnSaveDialogDone, this))))); } private: diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 5843e4e11d..821bd55063 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -155,7 +155,7 @@ dialog.showOpenDialog(mainWindow, { }) ``` -### `dialog.showSaveDialog([browserWindow, ]options[, callback])` +### `dialog.showSaveDialog([browserWindow, ]options)` * `browserWindow` [BrowserWindow](browser-window.md) (optional) * `options` Object @@ -171,22 +171,42 @@ dialog.showOpenDialog(mainWindow, { * `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, defaults to `true`. * `securityScopedBookmarks` Boolean (optional) _macOS_ _mas_ - Create a [security scoped bookmark](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) when packaged for the Mac App Store. If this option is enabled and the file doesn't already exist a blank file will be created at the chosen path. -* `callback` Function (optional) - * `filename` String (optional) If the dialog is cancelled this will be `undefined`. - * `bookmark` String (optional) _macOS_ _mas_ - Base64 encoded string which contains the security scoped bookmark data for the saved file. `securityScopedBookmarks` must be enabled for this to be present. -Returns `String | undefined`, the path of the file chosen by the user, -if a callback is provided or the dialog is cancelled it returns `undefined`. +Returns `String | undefined`, the path of the file chosen by the user; if the dialog is cancelled it returns `undefined`. The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. The `filters` specifies an array of file types that can be displayed, see `dialog.showOpenDialog` for an example. -If a `callback` is passed, the API call will be asynchronous and the result -will be passed via `callback(filename)`. +### `dialog.showSaveDialog([browserWindow, ]options)` -**Note:** On macOS, using the `callback` is recommended to avoid issues when +* `browserWindow` [BrowserWindow](browser-window.md) (optional) +* `options` Object + * `title` String (optional) + * `defaultPath` String (optional) - Absolute directory path, absolute file + path, or file name to use by default. + * `buttonLabel` String (optional) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) + * `message` String (optional) _macOS_ - Message to display above text fields. + * `nameFieldLabel` String (optional) _macOS_ - Custom label for the text + displayed in front of the filename text field. + * `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, + defaults to `true`. + * `securityScopedBookmarks` Boolean (optional) _macOS_ _mas_ - Create a [security scoped bookmark](https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16) when packaged for the Mac App Store. If this option is enabled and the file doesn't already exist a blank file will be created at the chosen path. + +Returns `Promise` - Resolve with an object containing the following: + * `canceled` Boolean - whether or not the dialog was canceled. + * `filePath` String (optional) If the dialog is canceled this will be `undefined`. + * `bookmark` String (optional) _macOS_ _mas_ - Base64 encoded string which contains the security scoped bookmark data for the saved file. `securityScopedBookmarks` must be enabled for this to be present. + +The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal. + +The `filters` specifies an array of file types that can be displayed, see +`dialog.showOpenDialog` for an example. + +**Note:** On macOS, using the asynchronous version is recommended to avoid issues when expanding and collapsing the dialog. ### `dialog.showMessageBox([browserWindow, ]options[, callback])` diff --git a/docs/api/promisification.md b/docs/api/promisification.md index c0d6f30e0f..42af84871a 100644 --- a/docs/api/promisification.md +++ b/docs/api/promisification.md @@ -9,7 +9,6 @@ When a majority of affected functions are migrated, this flag will be enabled by ### Candidate Functions - [app.importCertificate(options, callback)](https://github.com/electron/electron/blob/master/docs/api/app.md#importCertificate) -- [dialog.showSaveDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showSaveDialog) - [dialog.showMessageBox([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showMessageBox) - [dialog.showCertificateTrustDialog([browserWindow, ]options, callback)](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showCertificateTrustDialog) - [inAppPurchase.purchaseProduct(productID, quantity, callback)](https://github.com/electron/electron/blob/master/docs/api/in-app-purchase.md#purchaseProduct) @@ -44,8 +43,9 @@ When a majority of affected functions are migrated, this flag will be enabled by - [cookies.set(details, callback)](https://github.com/electron/electron/blob/master/docs/api/cookies.md#set) - [debugger.sendCommand(method[, commandParams, callback])](https://github.com/electron/electron/blob/master/docs/api/debugger.md#sendCommand) - [desktopCapturer.getSources(options, callback)](https://github.com/electron/electron/blob/master/docs/api/desktop-capturer.md#getSources) -- [netLog.stopLogging([callback])](https://github.com/electron/electron/blob/master/docs/api/net-log.md#stopLogging) - [dialog.showOpenDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showOpenDialog) +- [dialog.showSaveDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showSaveDialog) +- [netLog.stopLogging([callback])](https://github.com/electron/electron/blob/master/docs/api/net-log.md#stopLogging) - [protocol.isProtocolHandled(scheme, callback)](https://github.com/electron/electron/blob/master/docs/api/protocol.md#isProtocolHandled) - [shell.openExternal(url[, options, callback])](https://github.com/electron/electron/blob/master/docs/api/shell.md#openExternal) - [webviewTag.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#capturePage) diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 4e7032d385..272735fbea 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -70,6 +70,33 @@ const checkAppInitialized = function () { } } +const saveDialog = (sync, window, options) => { + checkAppInitialized() + + if (window.constructor !== BrowserWindow) options = window + if (options == null) options = { title: 'Save' } + + const { + buttonLabel = '', + defaultPath = '', + filters = [], + title = '', + message = '', + securityScopedBookmarks = false, + nameFieldLabel = '', + showsTagField = true + } = options + + if (typeof title !== 'string') throw new TypeError('Title must be a string') + if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') + if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') + if (typeof message !== 'string') throw new TypeError('Message must be a string') + if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string') + + const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window } + return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings) +} + const openDialog = (sync, window, options) => { checkAppInitialized() @@ -115,65 +142,17 @@ module.exports = { showOpenDialog: function (window, options) { return openDialog(false, window, options) }, + showOpenDialogSync: function (window, options) { return openDialog(true, window, options) }, - showSaveDialog: function (...args) { - checkAppInitialized() - let [window, options, callback] = parseArgs(...args) + showSaveDialog: function (window, options) { + return saveDialog(false, window, options) + }, - if (options == null) { - options = { - title: 'Save' - } - } - - let { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks = false, nameFieldLabel, showsTagField } = options - - if (title == null) { - title = '' - } else if (typeof title !== 'string') { - throw new TypeError('Title must be a string') - } - - if (buttonLabel == null) { - buttonLabel = '' - } else if (typeof buttonLabel !== 'string') { - throw new TypeError('Button label must be a string') - } - - if (defaultPath == null) { - defaultPath = '' - } else if (typeof defaultPath !== 'string') { - throw new TypeError('Default path must be a string') - } - - if (filters == null) { - filters = [] - } - - if (message == null) { - message = '' - } else if (typeof message !== 'string') { - throw new TypeError('Message must be a string') - } - - if (nameFieldLabel == null) { - nameFieldLabel = '' - } else if (typeof nameFieldLabel !== 'string') { - throw new TypeError('Name field label must be a string') - } - - if (showsTagField == null) { - showsTagField = true - } - - const wrappedCallback = typeof callback === 'function' ? function (success, result, bookmarkData) { - return success ? callback(result, bookmarkData) : callback() - } : null - const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window } - return binding.showSaveDialog(settings, wrappedCallback) + showSaveDialogSync: function (window, options) { + return saveDialog(true, window, options) }, showMessageBox: function (...args) { @@ -291,6 +270,7 @@ module.exports = { } module.exports.showOpenDialog = deprecate.promisify(module.exports.showOpenDialog) +module.exports.showSaveDialog = deprecate.promisify(module.exports.showSaveDialog) // Mark standard asynchronous functions. v8Util.setHiddenValue(module.exports.showMessageBox, 'asynchronous', true)