diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index 34f71380a..ae8410019 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -195,13 +195,15 @@ typedef struct _cef_run_file_dialog_callback_t { cef_base_t base; /// - // Called asynchronously after the file dialog is dismissed. If the selection - // was successful |file_paths| will be a single value or a list of values - // depending on the dialog mode. If the selection was cancelled |file_paths| - // will be NULL. + // Called asynchronously after the file dialog is dismissed. + // |selected_accept_filter| is the 0-based index of the value selected from + // the accept filters array passed to cef_browser_host_t::RunFileDialog. + // |file_paths| will be a single value or a list of values depending on the + // dialog mode. If the selection was cancelled |file_paths| will be NULL. /// - void (CEF_CALLBACK *cont)(struct _cef_run_file_dialog_callback_t* self, - struct _cef_browser_host_t* browser_host, cef_string_list_t file_paths); + void (CEF_CALLBACK *on_file_dialog_dismissed)( + struct _cef_run_file_dialog_callback_t* self, int selected_accept_filter, + cef_string_list_t file_paths); } cef_run_file_dialog_callback_t; @@ -315,17 +317,22 @@ typedef struct _cef_browser_host_t { // Call to run a file chooser dialog. Only a single file chooser dialog may be // pending at any given time. |mode| represents the type of dialog to display. // |title| to the title to be used for the dialog and may be NULL to show the - // default title ("Open" or "Save" depending on the mode). |default_file_name| - // is the default file name to select in the dialog. |accept_types| is a list - // of valid lower-cased MIME types or file extensions specified in an input - // element and is used to restrict selectable files to such types. |callback| - // will be executed after the dialog is dismissed or immediately if another - // dialog is already pending. The dialog will be initiated asynchronously on - // the UI thread. + // default title ("Open" or "Save" depending on the mode). |default_file_path| + // is the path with optional directory and/or file name component that will be + // initially selected in the dialog. |accept_filters| are used to restrict the + // selectable file types and may any combination of (a) valid lower-cased MIME + // types (e.g. "text/*" or "image/*"), (b) individual file extensions (e.g. + // ".txt" or ".png"), or (c) combined description and file extension delimited + // using "|" and ";" (e.g. "Image Types|.png;.gif;.jpg"). + // |selected_accept_filter| is the 0-based index of the filter that will be + // selected by default. |callback| will be executed after the dialog is + // dismissed or immediately if another dialog is already pending. The dialog + // will be initiated asynchronously on the UI thread. /// void (CEF_CALLBACK *run_file_dialog)(struct _cef_browser_host_t* self, cef_file_dialog_mode_t mode, const cef_string_t* title, - const cef_string_t* default_file_name, cef_string_list_t accept_types, + const cef_string_t* default_file_path, cef_string_list_t accept_filters, + int selected_accept_filter, struct _cef_run_file_dialog_callback_t* callback); /// diff --git a/include/capi/cef_dialog_handler_capi.h b/include/capi/cef_dialog_handler_capi.h index d2fa9cc8a..c97a9639f 100644 --- a/include/capi/cef_dialog_handler_capi.h +++ b/include/capi/cef_dialog_handler_capi.h @@ -56,12 +56,14 @@ typedef struct _cef_file_dialog_callback_t { cef_base_t base; /// - // Continue the file selection with the specified |file_paths|. This may be a - // single value or a list of values depending on the dialog mode. An NULL + // Continue the file selection. |selected_accept_filter| should be the 0-based + // index of the value selected from the accept filters array passed to + // cef_dialog_handler_t::OnFileDialog. |file_paths| should be a single value + // or a list of values depending on the dialog mode. An NULL |file_paths| // value is treated the same as calling cancel(). /// void (CEF_CALLBACK *cont)(struct _cef_file_dialog_callback_t* self, - cef_string_list_t file_paths); + int selected_accept_filter, cef_string_list_t file_paths); /// // Cancel the file selection. @@ -84,17 +86,21 @@ typedef struct _cef_dialog_handler_t { // Called to run a file chooser dialog. |mode| represents the type of dialog // to display. |title| to the title to be used for the dialog and may be NULL // to show the default title ("Open" or "Save" depending on the mode). - // |default_file_name| is the default file name to select in the dialog. - // |accept_types| is a list of valid lower-cased MIME types or file extensions - // specified in an input element and is used to restrict selectable files to - // such types. To display a custom dialog return true (1) and execute - // |callback| either inline or at a later time. To display the default dialog - // return false (0). + // |default_file_path| is the path with optional directory and/or file name + // component that should be initially selected in the dialog. |accept_filters| + // are used to restrict the selectable file types and may any combination of + // (a) valid lower-cased MIME types (e.g. "text/*" or "image/*"), (b) + // individual file extensions (e.g. ".txt" or ".png"), or (c) combined + // description and file extension delimited using "|" and ";" (e.g. "Image + // Types|.png;.gif;.jpg"). |selected_accept_filter| is the 0-based index of + // the filter that should be selected by default. To display a custom dialog + // return true (1) and execute |callback| either inline or at a later time. To + // display the default dialog return false (0). /// int (CEF_CALLBACK *on_file_dialog)(struct _cef_dialog_handler_t* self, struct _cef_browser_t* browser, cef_file_dialog_mode_t mode, - const cef_string_t* title, const cef_string_t* default_file_name, - cef_string_list_t accept_types, + const cef_string_t* title, const cef_string_t* default_file_path, + cef_string_list_t accept_filters, int selected_accept_filter, struct _cef_file_dialog_callback_t* callback); } cef_dialog_handler_t; diff --git a/include/cef_browser.h b/include/cef_browser.h index 4f7a34449..653053f0e 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -199,14 +199,15 @@ class CefBrowser : public virtual CefBase { class CefRunFileDialogCallback : public virtual CefBase { public: /// - // Called asynchronously after the file dialog is dismissed. If the selection - // was successful |file_paths| will be a single value or a list of values - // depending on the dialog mode. If the selection was cancelled |file_paths| - // will be empty. + // Called asynchronously after the file dialog is dismissed. + // |selected_accept_filter| is the 0-based index of the value selected from + // the accept filters array passed to CefBrowserHost::RunFileDialog. + // |file_paths| will be a single value or a list of values depending on the + // dialog mode. If the selection was cancelled |file_paths| will be empty. /// - /*--cef(capi_name=cont)--*/ + /*--cef(index_param=selected_accept_filter,optional_param=file_paths)--*/ virtual void OnFileDialogDismissed( - CefRefPtr browser_host, + int selected_accept_filter, const std::vector& file_paths) =0; }; @@ -355,20 +356,25 @@ class CefBrowserHost : public virtual CefBase { // Call to run a file chooser dialog. Only a single file chooser dialog may be // pending at any given time. |mode| represents the type of dialog to display. // |title| to the title to be used for the dialog and may be empty to show the - // default title ("Open" or "Save" depending on the mode). |default_file_name| - // is the default file name to select in the dialog. |accept_types| is a list - // of valid lower-cased MIME types or file extensions specified in an input - // element and is used to restrict selectable files to such types. |callback| - // will be executed after the dialog is dismissed or immediately if another - // dialog is already pending. The dialog will be initiated asynchronously on - // the UI thread. + // default title ("Open" or "Save" depending on the mode). |default_file_path| + // is the path with optional directory and/or file name component that will be + // initially selected in the dialog. |accept_filters| are used to restrict the + // selectable file types and may any combination of (a) valid lower-cased MIME + // types (e.g. "text/*" or "image/*"), (b) individual file extensions (e.g. + // ".txt" or ".png"), or (c) combined description and file extension delimited + // using "|" and ";" (e.g. "Image Types|.png;.gif;.jpg"). + // |selected_accept_filter| is the 0-based index of the filter that will be + // selected by default. |callback| will be executed after the dialog is + // dismissed or immediately if another dialog is already pending. The dialog + // will be initiated asynchronously on the UI thread. /// - /*--cef(optional_param=title,optional_param=default_file_name, - optional_param=accept_types)--*/ + /*--cef(optional_param=title,optional_param=default_file_path, + optional_param=accept_filters,index_param=selected_accept_filter)--*/ virtual void RunFileDialog(FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) =0; /// diff --git a/include/cef_dialog_handler.h b/include/cef_dialog_handler.h index 83e1048a4..93cd5de7c 100644 --- a/include/cef_dialog_handler.h +++ b/include/cef_dialog_handler.h @@ -48,13 +48,17 @@ class CefFileDialogCallback : public virtual CefBase { public: /// - // Continue the file selection with the specified |file_paths|. This may be - // a single value or a list of values depending on the dialog mode. An empty - // value is treated the same as calling Cancel(). + // Continue the file selection. |selected_accept_filter| should be the 0-based + // index of the value selected from the accept filters array passed to + // CefDialogHandler::OnFileDialog. |file_paths| should be a single value or a + // list of values depending on the dialog mode. An empty |file_paths| value is + // treated the same as calling Cancel(). /// - /*--cef(capi_name=cont)--*/ - virtual void Continue(const std::vector& file_paths) =0; - + /*--cef(capi_name=cont,index_param=selected_accept_filter, + optional_param=file_paths)--*/ + virtual void Continue(int selected_accept_filter, + const std::vector& file_paths) =0; + /// // Cancel the file selection. /// @@ -76,20 +80,25 @@ class CefDialogHandler : public virtual CefBase { // Called to run a file chooser dialog. |mode| represents the type of dialog // to display. |title| to the title to be used for the dialog and may be empty // to show the default title ("Open" or "Save" depending on the mode). - // |default_file_name| is the default file name to select in the dialog. - // |accept_types| is a list of valid lower-cased MIME types or file extensions - // specified in an input element and is used to restrict selectable files to - // such types. To display a custom dialog return true and execute |callback| - // either inline or at a later time. To display the default dialog return - // false. + // |default_file_path| is the path with optional directory and/or file name + // component that should be initially selected in the dialog. |accept_filters| + // are used to restrict the selectable file types and may any combination of + // (a) valid lower-cased MIME types (e.g. "text/*" or "image/*"), + // (b) individual file extensions (e.g. ".txt" or ".png"), or (c) combined + // description and file extension delimited using "|" and ";" (e.g. + // "Image Types|.png;.gif;.jpg"). |selected_accept_filter| is the 0-based + // index of the filter that should be selected by default. To display a custom + // dialog return true and execute |callback| either inline or at a later time. + // To display the default dialog return false. /// - /*--cef(optional_param=title,optional_param=default_file_name, - optional_param=accept_types)--*/ + /*--cef(optional_param=title,optional_param=default_file_path, + optional_param=accept_filters,index_param=selected_accept_filter)--*/ virtual bool OnFileDialog(CefRefPtr browser, FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) { return false; } diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index 6bf5d0d23..376e38247 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -1720,11 +1720,36 @@ typedef enum { /// FILE_DIALOG_OPEN_MULTIPLE, + /// + // Like Open, but selects a folder to open. + /// + FILE_DIALOG_OPEN_FOLDER, + /// // Allows picking a nonexistent file, and prompts to overwrite if the file // already exists. /// FILE_DIALOG_SAVE, + + /// + // General mask defining the bits used for the type values. + /// + FILE_DIALOG_TYPE_MASK = 0xFF, + + // Qualifiers. + // Any of the type values above can be augmented by one or more qualifiers. + // These qualifiers further define the dialog behavior. + + /// + // Prompt to overwrite if the user selects an existing file with the Save + // dialog. + /// + FILE_DIALOG_OVERWRITEPROMPT_FLAG = 0x01000000, + + /// + // Do not display read-only files. + /// + FILE_DIALOG_HIDEREADONLY_FLAG = 0x02000000, } cef_file_dialog_mode_t; /// diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index c91c41c36..0d83125b5 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -200,7 +200,8 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback { } } - void Continue(const std::vector& file_paths) override { + void Continue(int selected_accept_filter, + const std::vector& file_paths) override { if (CEF_CURRENTLY_ON_UIT()) { if (!callback_.is_null()) { std::vector vec; @@ -209,12 +210,13 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback { for (; it != file_paths.end(); ++it) vec.push_back(base::FilePath(*it)); } - callback_.Run(vec); + callback_.Run(selected_accept_filter, vec); callback_.Reset(); } } else { CEF_POST_TASK(CEF_UIT, - base::Bind(&CefFileDialogCallbackImpl::Continue, this, file_paths)); + base::Bind(&CefFileDialogCallbackImpl::Continue, this, + selected_accept_filter, file_paths)); } } @@ -243,7 +245,7 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback { const CefBrowserHostImpl::RunFileChooserCallback& callback) { CEF_REQUIRE_UIT(); std::vector file_paths; - callback.Run(file_paths); + callback.Run(0, file_paths); } CefBrowserHostImpl::RunFileChooserCallback callback_; @@ -251,32 +253,17 @@ class CefFileDialogCallbackImpl : public CefFileDialogCallback { IMPLEMENT_REFCOUNTING(CefFileDialogCallbackImpl); }; -class CefRunFileDialogCallbackWrapper - : public base::RefCountedThreadSafe { - public: - CefRunFileDialogCallbackWrapper(CefRefPtr host, - CefRefPtr callback) - : host_(host), - callback_(callback) { +void RunFileDialogDismissed( + CefRefPtr callback, + int selected_accept_filter, + const std::vector& file_paths) { + std::vector paths; + if (file_paths.size() > 0) { + for (size_t i = 0; i < file_paths.size(); ++i) + paths.push_back(file_paths[i].value()); } - - void Callback(const std::vector& file_paths) { - std::vector paths; - if (file_paths.size() > 0) { - for (size_t i = 0; i < file_paths.size(); ++i) - paths.push_back(file_paths[i].value()); - } - callback_->OnFileDialogDismissed(host_, paths); - } - - private: - friend class base::RefCountedThreadSafe; - - ~CefRunFileDialogCallbackWrapper() {} - - CefRefPtr host_; - CefRefPtr callback_; -}; + callback->OnFileDialogDismissed(selected_accept_filter, paths); +} } // namespace @@ -712,38 +699,47 @@ void CefBrowserHostImpl::SetZoomLevel(double zoomLevel) { void CefBrowserHostImpl::RunFileDialog( FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) { DCHECK(callback.get()); if (!callback.get()) return; - content::FileChooserParams params; - switch (mode) { + FileChooserParams params; + switch (mode & FILE_DIALOG_TYPE_MASK) { case FILE_DIALOG_OPEN: params.mode = content::FileChooserParams::Open; break; case FILE_DIALOG_OPEN_MULTIPLE: params.mode = content::FileChooserParams::OpenMultiple; break; + case FILE_DIALOG_OPEN_FOLDER: + params.mode = content::FileChooserParams::UploadFolder; + break; case FILE_DIALOG_SAVE: params.mode = content::FileChooserParams::Save; break; } + + DCHECK_GE(selected_accept_filter, 0); + params.selected_accept_filter = selected_accept_filter; + + params.overwriteprompt = !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG); + params.hidereadonly = !!(mode & FILE_DIALOG_HIDEREADONLY_FLAG); + params.title = title; - if (!default_file_name.empty()) - params.default_file_name = base::FilePath(default_file_name); - if (!accept_types.empty()) { - std::vector::const_iterator it = accept_types.begin(); - for (; it != accept_types.end(); ++it) + if (!default_file_path.empty()) + params.default_file_name = base::FilePath(default_file_path); + + if (!accept_filters.empty()) { + std::vector::const_iterator it = accept_filters.begin(); + for (; it != accept_filters.end(); ++it) params.accept_types.push_back(*it); } - scoped_refptr wrapper = - new CefRunFileDialogCallbackWrapper(this, callback); - RunFileChooser(params, - base::Bind(&CefRunFileDialogCallbackWrapper::Callback, wrapper)); + RunFileChooser(params, base::Bind(RunFileDialogDismissed, callback)); } void CefBrowserHostImpl::StartDownload(const CefString& url) { @@ -1773,7 +1769,7 @@ void CefBrowserHostImpl::OnSetFocus(cef_focus_source_t source) { } void CefBrowserHostImpl::RunFileChooser( - const content::FileChooserParams& params, + const FileChooserParams& params, const RunFileChooserCallback& callback) { CEF_POST_TASK(CEF_UIT, base::Bind(&CefBrowserHostImpl::RunFileChooserOnUIThread, this, params, @@ -2269,7 +2265,7 @@ void CefBrowserHostImpl::WebContentsCreated( } void CefBrowserHostImpl::DidNavigateMainFramePostCommit( - content::WebContents* tab) { + content::WebContents* web_contents) { base::AutoLock lock_scope(state_lock_); has_document_ = false; } @@ -2283,15 +2279,25 @@ content::JavaScriptDialogManager* } void CefBrowserHostImpl::RunFileChooser( - content::WebContents* tab, + content::WebContents* web_contents, const content::FileChooserParams& params) { - content::RenderViewHost* render_view_host = tab->GetRenderViewHost(); + content::RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); if (!render_view_host) return; - RunFileChooserOnUIThread(params, + if (params.mode == content::FileChooserParams::UploadFolder) { + // TODO(cef): Implement the logic necessary for the 'webkitdirectory' + // attribute. See CEF issue #958. + OnRunFileChooserDelegateCallback( + web_contents, params.mode, 0, std::vector()); + return; + } + + FileChooserParams cef_params; + static_cast(cef_params) = params; + RunFileChooserOnUIThread(cef_params, base::Bind(&CefBrowserHostImpl::OnRunFileChooserDelegateCallback, this, - tab, params.mode)); + web_contents, params.mode)); } bool CefBrowserHostImpl::SetPendingPopupInfo( @@ -2931,19 +2937,13 @@ void CefBrowserHostImpl::OnLoadEnd(CefRefPtr frame, } void CefBrowserHostImpl::RunFileChooserOnUIThread( - const content::FileChooserParams& params, + const FileChooserParams& params, const RunFileChooserCallback& callback) { CEF_REQUIRE_UIT(); if (file_chooser_pending_) { // Dismiss the new dialog immediately. - callback.Run(std::vector()); - return; - } - - if (params.mode == content::FileChooserParams::UploadFolder) { - NOTIMPLEMENTED(); - callback.Run(std::vector()); + callback.Run(0, std::vector()); return; } @@ -2958,7 +2958,7 @@ void CefBrowserHostImpl::RunFileChooserOnUIThread( if (client_.get()) { CefRefPtr handler = client_->GetDialogHandler(); if (handler.get()) { - cef_file_dialog_mode_t mode = FILE_DIALOG_OPEN; + int mode = FILE_DIALOG_OPEN; switch (params.mode) { case content::FileChooserParams::Open: mode = FILE_DIALOG_OPEN; @@ -2966,6 +2966,9 @@ void CefBrowserHostImpl::RunFileChooserOnUIThread( case content::FileChooserParams::OpenMultiple: mode = FILE_DIALOG_OPEN_MULTIPLE; break; + case content::FileChooserParams::UploadFolder: + mode = FILE_DIALOG_OPEN_FOLDER; + break; case content::FileChooserParams::Save: mode = FILE_DIALOG_SAVE; break; @@ -2974,17 +2977,28 @@ void CefBrowserHostImpl::RunFileChooserOnUIThread( break; } - std::vector accept_types; - std::vector::const_iterator it = - params.accept_types.begin(); + if (params.overwriteprompt) + mode |= FILE_DIALOG_OVERWRITEPROMPT_FLAG; + if (params.hidereadonly) + mode |= FILE_DIALOG_HIDEREADONLY_FLAG; + + std::vector::const_iterator it; + + std::vector accept_filters; + it = params.accept_types.begin(); for (; it != params.accept_types.end(); ++it) - accept_types.push_back(*it); + accept_filters.push_back(*it); CefRefPtr callbackImpl( new CefFileDialogCallbackImpl(host_callback)); - handled = handler->OnFileDialog(this, mode, params.title, - params.default_file_name.value(), - accept_types, callbackImpl.get()); + handled = handler->OnFileDialog( + this, + static_cast(mode), + params.title, + params.default_file_name.value(), + accept_filters, + params.selected_accept_filter, + callbackImpl.get()); if (!handled) { if (callbackImpl->IsConnected()) { callbackImpl->Disconnect(); @@ -3003,22 +3017,25 @@ void CefBrowserHostImpl::RunFileChooserOnUIThread( void CefBrowserHostImpl::OnRunFileChooserCallback( const RunFileChooserCallback& callback, + int selected_accept_filter, const std::vector& file_paths) { CEF_REQUIRE_UIT(); file_chooser_pending_ = false; // Execute the callback asynchronously. - CEF_POST_TASK(CEF_UIT, base::Bind(callback, file_paths)); + CEF_POST_TASK(CEF_UIT, + base::Bind(callback, selected_accept_filter, file_paths)); } void CefBrowserHostImpl::OnRunFileChooserDelegateCallback( - content::WebContents* tab, + content::WebContents* web_contents, content::FileChooserParams::Mode mode, + int selected_accept_filter, const std::vector& file_paths) { CEF_REQUIRE_UIT(); - content::RenderViewHost* render_view_host = tab->GetRenderViewHost(); + content::RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); if (!render_view_host) return; diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index 916bea3ea..630fda3d7 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -97,6 +97,18 @@ class CefBrowserHostImpl : public CefBrowserHost, virtual void OnResponse(const std::string& response) =0; }; + // Extend content::FileChooserParams with some options unique to CEF. + struct FileChooserParams : public content::FileChooserParams { + // 0-based index of the selected value in |accept_types|. + int selected_accept_filter = 0; + + // True if the Save dialog should prompt before overwriting files. + bool overwriteprompt = true; + + // True if read-only files should be hidden. + bool hidereadonly = true; + }; + ~CefBrowserHostImpl() override; // Create a new CefBrowserHostImpl instance. @@ -142,8 +154,9 @@ class CefBrowserHostImpl : public CefBrowserHost, void RunFileDialog( FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) override; void StartDownload(const CefString& url) override; void Print() override; @@ -307,13 +320,13 @@ class CefBrowserHostImpl : public CefBrowserHost, void OnSetFocus(cef_focus_source_t source); // The argument vector will be empty if the dialog was cancelled. - typedef base::Callback&)> + typedef base::Callback&)> RunFileChooserCallback; // Run the file chooser dialog specified by |params|. Only a single dialog may // be pending at any given time. |callback| will be executed asynchronously // after the dialog is dismissed or if another dialog is already pending. - void RunFileChooser(const content::FileChooserParams& params, + void RunFileChooser(const FileChooserParams& params, const RunFileChooserCallback& callback); // Used when creating a new popup window. @@ -383,11 +396,11 @@ class CefBrowserHostImpl : public CefBrowserHost, const GURL& target_url, content::WebContents* new_contents) override; void DidNavigateMainFramePostCommit( - content::WebContents* tab) override; + content::WebContents* web_contents) override; content::JavaScriptDialogManager* GetJavaScriptDialogManager( content::WebContents* source) override; void RunFileChooser( - content::WebContents* tab, + content::WebContents* web_contents, const content::FileChooserParams& params) override; void FindReply( content::WebContents* web_contents, @@ -528,7 +541,7 @@ class CefBrowserHostImpl : public CefBrowserHost, // Invoke platform specific handling for the external protocol. void PlatformHandleExternalProtocol(const GURL& url); // Invoke platform specific file chooser dialog. - void PlatformRunFileChooser(const content::FileChooserParams& params, + void PlatformRunFileChooser(const FileChooserParams& params, RunFileChooserCallback callback); void PlatformTranslateKeyEvent(content::NativeWebKeyboardEvent& native_event, @@ -565,17 +578,19 @@ class CefBrowserHostImpl : public CefBrowserHost, int http_status_code); // Continuation from RunFileChooser. - void RunFileChooserOnUIThread(const content::FileChooserParams& params, + void RunFileChooserOnUIThread(const FileChooserParams& params, const RunFileChooserCallback& callback); // Used with RunFileChooser to clear the |file_chooser_pending_| flag. void OnRunFileChooserCallback(const RunFileChooserCallback& callback, + int selected_accept_filter, const std::vector& file_paths); // Used with WebContentsDelegate::RunFileChooser to notify the WebContents. void OnRunFileChooserDelegateCallback( - content::WebContents* tab, + content::WebContents* web_contents, content::FileChooserParams::Mode mode, + int selected_accept_filter, const std::vector& file_paths); void OnDevToolsWebContentsDestroyed(); diff --git a/libcef/browser/browser_host_impl_linux.cc b/libcef/browser/browser_host_impl_linux.cc index 54009239d..d8bafcb85 100644 --- a/libcef/browser/browser_host_impl_linux.cc +++ b/libcef/browser/browser_host_impl_linux.cc @@ -282,11 +282,10 @@ void CefBrowserHostImpl::PlatformHandleKeyboardEvent( } void CefBrowserHostImpl::PlatformRunFileChooser( - const content::FileChooserParams& params, + const FileChooserParams& params, RunFileChooserCallback callback) { NOTIMPLEMENTED(); - std::vector files; - callback.Run(files); + callback.Run(0, std::vector()); } void CefBrowserHostImpl::PlatformHandleExternalProtocol(const GURL& url) { diff --git a/libcef/browser/browser_host_impl_mac.mm b/libcef/browser/browser_host_impl_mac.mm index fed060f15..34d553288 100644 --- a/libcef/browser/browser_host_impl_mac.mm +++ b/libcef/browser/browser_host_impl_mac.mm @@ -15,6 +15,7 @@ #include "base/files/file_util.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_nsautorelease_pool.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" @@ -23,6 +24,7 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "content/public/common/file_chooser_params.h" +#include "grit/cef_strings.h" #include "grit/ui_strings.h" #include "net/base/mime_util.h" #include "third_party/WebKit/public/web/WebInputEvent.h" @@ -170,33 +172,216 @@ @end +namespace { + +base::string16 GetDescriptionFromMimeType(const std::string& mime_type) { + // Check for wild card mime types and return an appropriate description. + static const struct { + const char* mime_type; + int string_id; + } kWildCardMimeTypes[] = { + { "audio", IDS_APP_AUDIO_FILES }, + { "image", IDS_APP_IMAGE_FILES }, + { "text", IDS_APP_TEXT_FILES }, + { "video", IDS_APP_VIDEO_FILES }, + }; + + for (size_t i = 0; i < arraysize(kWildCardMimeTypes); ++i) { + if (mime_type == std::string(kWildCardMimeTypes[i].mime_type) + "/*") + return l10n_util::GetStringUTF16(kWildCardMimeTypes[i].string_id); + } + + return base::string16(); +} + +void AddFilters(NSPopUpButton *button, + const std::vector& accept_filters, + bool include_all_files, + std::vector >* all_extensions) { + for (size_t i = 0; i < accept_filters.size(); ++i) { + const base::string16& filter = accept_filters[i]; + if (filter.empty()) + continue; + + std::vector extensions; + base::string16 description; + + size_t sep_index = filter.find('|'); + if (sep_index != std::string::npos) { + // Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3". + description = filter.substr(0, sep_index); + + std::vector ext; + base::SplitString(filter.substr(sep_index + 1), ';', &ext); + for (size_t x = 0; x < ext.size(); ++x) { + const base::string16& file_ext = ext[x]; + if (!file_ext.empty() && file_ext[0] == '.') + extensions.push_back(file_ext); + } + } else if (filter[0] == '.') { + // Treat as an extension beginning with the '.' character. + extensions.push_back(filter); + } else { + // Otherwise convert mime type to one or more extensions. + const std::string& ascii = base::UTF16ToASCII(filter); + std::vector ext; + net::GetExtensionsForMimeType(ascii, &ext); + if (!ext.empty()) { + for (size_t x = 0; x < ext.size(); ++x) + extensions.push_back(base::ASCIIToUTF16("." + ext[x])); + description = GetDescriptionFromMimeType(ascii); + } + } + + if (extensions.empty()) + continue; + + // Don't display a crazy number of extensions since the NSPopUpButton width + // will keep growing. + const size_t kMaxExtensions = 10; + + base::string16 ext_str; + for (size_t x = 0; x < std::min(kMaxExtensions, extensions.size()); ++x) { + const base::string16& pattern = base::ASCIIToUTF16("*") + extensions[x]; + if (x != 0) + ext_str += base::ASCIIToUTF16(";"); + ext_str += pattern; + } + + if (extensions.size() > kMaxExtensions) + ext_str += base::ASCIIToUTF16(";..."); + + if (description.empty()) { + description = ext_str; + } else { + description += + base::ASCIIToUTF16(" (") + ext_str + base::ASCIIToUTF16(")"); + } + + [button addItemWithTitle:base::SysUTF16ToNSString(description)]; + + all_extensions->push_back(extensions); + } + + // Add the *.* filter, but only if we have added other filters (otherwise it + // is implied). + if (include_all_files && !all_extensions->empty()) { + [button addItemWithTitle:base::SysUTF8ToNSString("All Files (*)")]; + all_extensions->push_back(std::vector()); + } +} + +} // namespace + +// Used to manage the file type filter in the NSSavePanel/NSOpenPanel. +@interface CefFilterDelegate : NSObject { + @private + NSSavePanel* panel_; + std::vector > extensions_; + int selected_index_; +} +- (id)initWithPanel:(NSSavePanel*)panel + andAcceptFilters:(const std::vector&)accept_filters + andFilterIndex:(int)index; +- (void)setFilter:(int)index; +- (int)filter; +- (void)filterSelectionChanged:(id)sender; +- (void)setFileExtension; +@end + +@implementation CefFilterDelegate + +- (id)initWithPanel:(NSSavePanel*)panel + andAcceptFilters:(const std::vector&)accept_filters + andFilterIndex:(int)index { + if (self = [super init]) { + DCHECK(panel); + panel_ = panel; + selected_index_ = 0; + + NSPopUpButton *button = [[NSPopUpButton alloc] init]; + AddFilters(button, accept_filters, true, &extensions_); + [button sizeToFit]; + [button setTarget:self]; + [button setAction:@selector(filterSelectionChanged:)]; + + if (index < static_cast(extensions_.size())) { + [button selectItemAtIndex:index]; + [self setFilter:index]; + } + + [panel_ setAccessoryView:button]; + } + return self; +} + +// Set the current filter index. +- (void)setFilter:(int)index { + DCHECK(index >= 0 && index < static_cast(extensions_.size())); + selected_index_ = index; + + // Set the selectable file types. For open panels this limits the files that + // can be selected. For save panels this applies a default file extenion when + // the dialog is dismissed if none is already provided. + NSMutableArray* acceptArray = nil; + if (!extensions_[index].empty()) { + acceptArray = [[NSMutableArray alloc] init]; + for (size_t i = 0; i < extensions_[index].size(); ++i) { + [acceptArray addObject: + base::SysUTF16ToNSString(extensions_[index][i].substr(1))]; + } + } + [panel_ setAllowedFileTypes:acceptArray]; + + if (![panel_ isKindOfClass:[NSOpenPanel class]]) { + // For save panels set the file extension. + [self setFileExtension]; + } +} + +// Returns the current filter index. +- (int)filter { + return selected_index_; +} + +// Called when the selected filter is changed via the NSPopUpButton. +- (void)filterSelectionChanged:(id)sender { + NSPopUpButton *button = (NSPopUpButton*)sender; + [self setFilter:[button indexOfSelectedItem]]; +} + +// Set the extension on the currently selected file name. +- (void)setFileExtension { + const std::vector& filter = extensions_[selected_index_]; + if (filter.empty()) { + // All extensions are allowed so don't change anything. + return; + } + + base::FilePath path(base::SysNSStringToUTF8([panel_ nameFieldStringValue])); + + // If the file name currently includes an extension from |filter| then don't + // change anything. + base::string16 extension = base::UTF8ToUTF16(path.Extension()); + if (!extension.empty()) { + for (size_t i = 0; i < filter.size(); ++i) { + if (filter[i] == extension) + return; + } + } + + // Change the extension to the first value in |filter|. + path = path.ReplaceExtension(base::UTF16ToUTF8(filter[0])); + [panel_ setNameFieldStringValue:base::SysUTF8ToNSString(path.value())]; +} + +@end namespace { -// Accept-types to file-types helper. -NSMutableArray* GetFileTypesFromAcceptTypes( - const std::vector& accept_types) { - NSMutableArray* acceptArray = [[NSMutableArray alloc] init]; - for (size_t i=0; i ext; - net::GetExtensionsForMimeType(ascii_type, &ext); - for (size_t x = 0; x < ext.size(); ++x) - [acceptArray addObject:base::SysUTF8ToNSString(ext[x])]; - } - } - } - return acceptArray; -} - -void RunOpenFileDialog(const content::FileChooserParams& params, +void RunOpenFileDialog(const CefBrowserHostImpl::FileChooserParams& params, NSView* view, + int* filter_index, std::vector* files) { NSOpenPanel* openPanel = [NSOpenPanel openPanel]; @@ -206,39 +391,51 @@ void RunOpenFileDialog(const content::FileChooserParams& params, } else { title = l10n_util::GetStringUTF16( params.mode == content::FileChooserParams::Open ? - IDS_OPEN_FILE_DIALOG_TITLE : IDS_OPEN_FILES_DIALOG_TITLE); + IDS_OPEN_FILE_DIALOG_TITLE : + (params.mode == content::FileChooserParams::OpenMultiple ? + IDS_OPEN_FILES_DIALOG_TITLE : IDS_SELECT_FOLDER_DIALOG_TITLE)); } [openPanel setTitle:base::SysUTF16ToNSString(title)]; - // Consider default file name if any. - base::FilePath default_file_name(params.default_file_name); - - if (!default_file_name.empty()) { - if (!default_file_name.BaseName().empty()) { - NSString* defaultName = base::SysUTF8ToNSString( - default_file_name.BaseName().value()); - [openPanel setNameFieldStringValue:defaultName]; - } - - if (!default_file_name.DirName().empty()) { - NSString* defaultDir = base::SysUTF8ToNSString( - default_file_name.DirName().value()); - [openPanel setDirectoryURL:[NSURL fileURLWithPath:defaultDir]]; + std::string filename, directory; + if (!params.default_file_name.empty()) { + if (params.mode == content::FileChooserParams::UploadFolder || + params.default_file_name.EndsWithSeparator()) { + // The value is only a directory. + directory = params.default_file_name.value(); + } else { + // The value is a file name and possibly a directory. + filename = params.default_file_name.BaseName().value(); + directory = params.default_file_name.DirName().value(); } } + if (!filename.empty()) { + [openPanel setNameFieldStringValue:base::SysUTF8ToNSString(filename)]; + } + if (!directory.empty()) { + [openPanel setDirectoryURL: + [NSURL fileURLWithPath:base::SysUTF8ToNSString(directory)]]; + } - // Consider supported file types - if (!params.accept_types.empty()) { - [openPanel setAllowedFileTypes:GetFileTypesFromAcceptTypes( - params.accept_types)]; + CefFilterDelegate* filter_delegate = nil; + if (params.mode != content::FileChooserParams::UploadFolder && + !params.accept_types.empty()) { + // Add the file filter control. + filter_delegate = + [[CefFilterDelegate alloc] initWithPanel:openPanel + andAcceptFilters:params.accept_types + andFilterIndex:*filter_index]; } // Further panel configuration. [openPanel setAllowsOtherFileTypes:YES]; [openPanel setAllowsMultipleSelection: (params.mode == content::FileChooserParams::OpenMultiple)]; - [openPanel setCanChooseFiles:YES]; - [openPanel setCanChooseDirectories:NO]; + [openPanel setCanChooseFiles: + (params.mode != content::FileChooserParams::UploadFolder)]; + [openPanel setCanChooseDirectories: + (params.mode == content::FileChooserParams::UploadFolder)]; + [openPanel setShowsHiddenFiles:!params.hidereadonly]; // Show panel. [openPanel beginSheetModalForWindow:[view window] completionHandler:nil]; @@ -251,11 +448,16 @@ void RunOpenFileDialog(const content::FileChooserParams& params, files->push_back(base::FilePath(base::SysNSStringToUTF8([url path]))); } } + + if (filter_delegate != nil) + *filter_index = [filter_delegate filter]; + [NSApp endSheet:openPanel]; } -bool RunSaveFileDialog(const content::FileChooserParams& params, +bool RunSaveFileDialog(const CefBrowserHostImpl::FileChooserParams& params, NSView* view, + int* filter_index, base::FilePath* file) { NSSavePanel* savePanel = [NSSavePanel savePanel]; @@ -266,30 +468,36 @@ bool RunSaveFileDialog(const content::FileChooserParams& params, title = l10n_util::GetStringUTF16(IDS_SAVE_AS_DIALOG_TITLE); [savePanel setTitle:base::SysUTF16ToNSString(title)]; - // Consider default file name if any. - base::FilePath default_file_name(params.default_file_name); - - if (!default_file_name.empty()) { - if (!default_file_name.BaseName().empty()) { - NSString* defaultName = base::SysUTF8ToNSString( - default_file_name.BaseName().value()); - [savePanel setNameFieldStringValue:defaultName]; - } - - if (!default_file_name.DirName().empty()) { - NSString* defaultDir = base::SysUTF8ToNSString( - default_file_name.DirName().value()); - [savePanel setDirectoryURL:[NSURL fileURLWithPath:defaultDir]]; + std::string filename, directory; + if (!params.default_file_name.empty()) { + if (params.default_file_name.EndsWithSeparator()) { + // The value is only a directory. + directory = params.default_file_name.value(); + } else { + // The value is a file name and possibly a directory. + filename = params.default_file_name.BaseName().value(); + directory = params.default_file_name.DirName().value(); } } + if (!filename.empty()) { + [savePanel setNameFieldStringValue:base::SysUTF8ToNSString(filename)]; + } + if (!directory.empty()) { + [savePanel setDirectoryURL: + [NSURL fileURLWithPath:base::SysUTF8ToNSString(directory)]]; + } - // Consider supported file types + CefFilterDelegate* filter_delegate = nil; if (!params.accept_types.empty()) { - [savePanel setAllowedFileTypes:GetFileTypesFromAcceptTypes( - params.accept_types)]; + // Add the file filter control. + filter_delegate = + [[CefFilterDelegate alloc] initWithPanel:savePanel + andAcceptFilters:params.accept_types + andFilterIndex:*filter_index]; } [savePanel setAllowsOtherFileTypes:YES]; + [savePanel setShowsHiddenFiles:!params.hidereadonly]; bool success = false; @@ -300,6 +508,10 @@ bool RunSaveFileDialog(const content::FileChooserParams& params, *file = base::FilePath([path UTF8String]); success = true; } + + if (filter_delegate != nil) + *filter_index = [filter_delegate filter]; + [NSApp endSheet:savePanel]; return success; @@ -491,22 +703,26 @@ void CefBrowserHostImpl::PlatformHandleKeyboardEvent( } void CefBrowserHostImpl::PlatformRunFileChooser( - const content::FileChooserParams& params, + const FileChooserParams& params, RunFileChooserCallback callback) { std::vector files; + int filter_index = params.selected_accept_filter; + NSView* owner = PlatformGetWindowHandle(); if (params.mode == content::FileChooserParams::Open || - params.mode == content::FileChooserParams::OpenMultiple) { - RunOpenFileDialog(params, PlatformGetWindowHandle(), &files); + params.mode == content::FileChooserParams::OpenMultiple || + params.mode == content::FileChooserParams::UploadFolder) { + RunOpenFileDialog(params, owner, &filter_index, &files); } else if (params.mode == content::FileChooserParams::Save) { base::FilePath file; - if (RunSaveFileDialog(params, PlatformGetWindowHandle(), &file)) + if (RunSaveFileDialog(params, owner, &filter_index, &file)) { files.push_back(file); + } } else { NOTIMPLEMENTED(); } - callback.Run(files); + callback.Run(filter_index, files); } void CefBrowserHostImpl::PlatformHandleExternalProtocol(const GURL& url) { diff --git a/libcef/browser/browser_host_impl_win.cc b/libcef/browser/browser_host_impl_win.cc index b8e694553..c1e286079 100644 --- a/libcef/browser/browser_host_impl_win.cc +++ b/libcef/browser/browser_host_impl_win.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -19,9 +20,11 @@ #include "base/files/file_util.h" #include "base/i18n/case_conversion.h" #include "base/memory/ref_counted_memory.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" +#include "base/win/scoped_comptr.h" #include "base/win/windows_version.h" #include "content/common/cursors/webcursor.h" #include "content/public/browser/native_web_keyboard_event.h" @@ -140,14 +143,8 @@ std::wstring FormatFilterForExtensions( ext_name = ext_name.substr(ext_index); if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) { - // The extension doesn't exist in the registry. Create a description - // based on the unknown extension type (i.e. if the extension is .qqq, - // the we create a description "QQQ File (.qqq)"). + // The extension doesn't exist in the registry. include_all_files = true; - desc = l10n_util::GetStringFUTF16( - IDS_APP_SAVEAS_EXTENSION_FORMAT, - base::i18n::ToUpper(base::WideToUTF16(ext_name)), - ext_name); } } @@ -189,33 +186,54 @@ std::wstring GetDescriptionFromMimeType(const std::string& mime_type) { return std::wstring(); } -std::wstring GetFilterStringFromAcceptTypes( - const std::vector& accept_types) { +std::wstring GetFilterString( + const std::vector& accept_filters) { std::vector extensions; std::vector descriptions; - for (size_t i = 0; i < accept_types.size(); ++i) { - std::string ascii_type = base::UTF16ToASCII(accept_types[i]); - if (ascii_type.length()) { - // Just treat as extension if contains '.' as the first character. - if (ascii_type[0] == '.') { - extensions.push_back(L"*" + base::ASCIIToUTF16(ascii_type)); - descriptions.push_back(std::wstring()); - } else { - // Otherwise convert mime type to one or more extensions. - std::vector ext; - std::wstring ext_str; - net::GetExtensionsForMimeType(ascii_type, &ext); - if (ext.size() > 0) { - for (size_t x = 0; x < ext.size(); ++x) { - if (x != 0) - ext_str += L";"; - ext_str += L"*." + ext[x]; - } - extensions.push_back(ext_str); - descriptions.push_back(GetDescriptionFromMimeType(ascii_type)); + for (size_t i = 0; i < accept_filters.size(); ++i) { + const base::string16& filter = accept_filters[i]; + if (filter.empty()) + continue; + + size_t sep_index = filter.find('|'); + if (sep_index != base::string16::npos) { + // Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3". + const base::string16& desc = filter.substr(0, sep_index); + std::vector ext; + base::SplitString(filter.substr(sep_index + 1), ';', &ext); + std::wstring ext_str; + for (size_t x = 0; x < ext.size(); ++x) { + const base::string16& file_ext = ext[x]; + if (!file_ext.empty() && file_ext[0] == '.') { + if (!ext_str.empty()) + ext_str += L";"; + ext_str += L"*" + file_ext; } } + if (!ext_str.empty()) { + extensions.push_back(ext_str); + descriptions.push_back(desc); + } + } else if (filter[0] == L'.') { + // Treat as an extension beginning with the '.' character. + extensions.push_back(L"*" + filter); + descriptions.push_back(std::wstring()); + } else { + // Otherwise convert mime type to one or more extensions. + const std::string& ascii = base::UTF16ToASCII(filter); + std::vector ext; + std::wstring ext_str; + net::GetExtensionsForMimeType(ascii, &ext); + if (!ext.empty()) { + for (size_t x = 0; x < ext.size(); ++x) { + if (x != 0) + ext_str += L";"; + ext_str += L"*." + ext[x]; + } + extensions.push_back(ext_str); + descriptions.push_back(GetDescriptionFromMimeType(ascii)); + } } } @@ -224,8 +242,9 @@ std::wstring GetFilterStringFromAcceptTypes( // from chrome/browser/views/shell_dialogs_win.cc -bool RunOpenFileDialog(const content::FileChooserParams& params, +bool RunOpenFileDialog(const CefBrowserHostImpl::FileChooserParams& params, HWND owner, + int* filter_index, base::FilePath* path) { OPENFILENAME ofn; @@ -235,22 +254,25 @@ bool RunOpenFileDialog(const content::FileChooserParams& params, ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = owner; - // Consider default file name if any. - base::FilePath default_file_name(params.default_file_name); - wchar_t filename[MAX_PATH] = {0}; ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; std::wstring directory; - if (!default_file_name.empty()) { - base::wcslcpy(filename, default_file_name.value().c_str(), - arraysize(filename)); - - directory = default_file_name.DirName().value(); - ofn.lpstrInitialDir = directory.c_str(); + if (!params.default_file_name.empty()) { + if (params.default_file_name.EndsWithSeparator()) { + // The value is only a directory. + directory = params.default_file_name.value(); + } else { + // The value is a file name and possibly a directory. + base::wcslcpy(filename, params.default_file_name.value().c_str(), + arraysize(filename)); + directory = params.default_file_name.DirName().value(); + } } + if (!directory.empty()) + ofn.lpstrInitialDir = directory.c_str(); std::wstring title; if (!params.title.empty()) @@ -264,19 +286,27 @@ bool RunOpenFileDialog(const content::FileChooserParams& params, // without having to close Chrome first. ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_ENABLESIZING; + if (params.hidereadonly) + ofn.Flags |= OFN_HIDEREADONLY; - std::wstring filter = GetFilterStringFromAcceptTypes(params.accept_types); - if (!filter.empty()) + const std::wstring& filter = GetFilterString(params.accept_types); + if (!filter.empty()) { ofn.lpstrFilter = filter.c_str(); + // Indices into |lpstrFilter| start at 1. + ofn.nFilterIndex = *filter_index + 1; + } bool success = !!GetOpenFileName(&ofn); - if (success) + if (success) { + *filter_index = ofn.nFilterIndex == 0 ? 0 : ofn.nFilterIndex - 1; *path = base::FilePath(filename); + } return success; } -bool RunOpenMultiFileDialog(const content::FileChooserParams& params, +bool RunOpenMultiFileDialog(const CefBrowserHostImpl::FileChooserParams& params, HWND owner, + int* filter_index, std::vector* paths) { OPENFILENAME ofn; @@ -292,6 +322,19 @@ bool RunOpenMultiFileDialog(const content::FileChooserParams& params, ofn.lpstrFile = filename.get(); ofn.nMaxFile = UNICODE_STRING_MAX_CHARS; + std::wstring directory; + if (!params.default_file_name.empty()) { + if (params.default_file_name.EndsWithSeparator()) { + // The value is only a directory. + directory = params.default_file_name.value(); + } else { + // The value is a file name and possibly a directory. + directory = params.default_file_name.DirName().value(); + } + } + if (!directory.empty()) + ofn.lpstrInitialDir = directory.c_str(); + std::wstring title; if (!params.title.empty()) title = params.title; @@ -303,11 +346,16 @@ bool RunOpenMultiFileDialog(const content::FileChooserParams& params, // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory // without having to close Chrome first. ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER | - OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT | OFN_ENABLESIZING; + OFN_ALLOWMULTISELECT | OFN_ENABLESIZING; + if (params.hidereadonly) + ofn.Flags |= OFN_HIDEREADONLY; - std::wstring filter = GetFilterStringFromAcceptTypes(params.accept_types); - if (!filter.empty()) + const std::wstring& filter = GetFilterString(params.accept_types); + if (!filter.empty()) { ofn.lpstrFilter = filter.c_str(); + // Indices into |lpstrFilter| start at 1. + ofn.nFilterIndex = *filter_index + 1; + } bool success = !!GetOpenFileName(&ofn); @@ -334,11 +382,84 @@ bool RunOpenMultiFileDialog(const content::FileChooserParams& params, } } } + + if (success) + *filter_index = ofn.nFilterIndex == 0 ? 0 : ofn.nFilterIndex - 1; + return success; } -bool RunSaveFileDialog(const content::FileChooserParams& params, +// The callback function for when the select folder dialog is opened. +int CALLBACK BrowseCallbackProc(HWND window, + UINT message, + LPARAM parameter, + LPARAM data) +{ + if (message == BFFM_INITIALIZED) { + // WParam is TRUE since passing a path. + // data lParam member of the BROWSEINFO structure. + SendMessage(window, BFFM_SETSELECTION, TRUE, (LPARAM)data); + } + return 0; +} + +bool RunOpenFolderDialog(const CefBrowserHostImpl::FileChooserParams& params, + HWND owner, + base::FilePath* path) { + wchar_t dir_buffer[MAX_PATH + 1] = {0}; + + bool result = false; + BROWSEINFO browse_info = {0}; + browse_info.hwndOwner = owner; + browse_info.pszDisplayName = dir_buffer; + browse_info.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + + std::wstring title; + if (!params.title.empty()) + title = params.title; + else + title = l10n_util::GetStringUTF16(IDS_SELECT_FOLDER_DIALOG_TITLE); + if (!title.empty()) + browse_info.lpszTitle = title.c_str(); + + const std::wstring& file_path = params.default_file_name.value(); + if (!file_path.empty()) { + // Highlight the current value. + browse_info.lParam = (LPARAM)file_path.c_str(); + browse_info.lpfn = &BrowseCallbackProc; + } + + LPITEMIDLIST list = SHBrowseForFolder(&browse_info); + if (list) { + STRRET out_dir_buffer; + ZeroMemory(&out_dir_buffer, sizeof(out_dir_buffer)); + out_dir_buffer.uType = STRRET_WSTR; + base::win::ScopedComPtr shell_folder; + if (SHGetDesktopFolder(shell_folder.Receive()) == NOERROR) { + HRESULT hr = shell_folder->GetDisplayNameOf(list, SHGDN_FORPARSING, + &out_dir_buffer); + if (SUCCEEDED(hr) && out_dir_buffer.uType == STRRET_WSTR) { + *path = base::FilePath(out_dir_buffer.pOleStr); + CoTaskMemFree(out_dir_buffer.pOleStr); + result = true; + } else { + // Use old way if we don't get what we want. + wchar_t old_out_dir_buffer[MAX_PATH + 1]; + if (SHGetPathFromIDList(list, old_out_dir_buffer)) { + *path = base::FilePath(old_out_dir_buffer); + result = true; + } + } + } + CoTaskMemFree(list); + } + + return result; +} + +bool RunSaveFileDialog(const CefBrowserHostImpl::FileChooserParams& params, HWND owner, + int* filter_index, base::FilePath* path) { OPENFILENAME ofn; @@ -348,22 +469,25 @@ bool RunSaveFileDialog(const content::FileChooserParams& params, ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = owner; - // Consider default file name if any. - base::FilePath default_file_name(params.default_file_name); - wchar_t filename[MAX_PATH] = {0}; ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; std::wstring directory; - if (!default_file_name.empty()) { - base::wcslcpy(filename, default_file_name.value().c_str(), - arraysize(filename)); - - directory = default_file_name.DirName().value(); - ofn.lpstrInitialDir = directory.c_str(); + if (!params.default_file_name.empty()) { + if (params.default_file_name.EndsWithSeparator()) { + // The value is only a directory. + directory = params.default_file_name.value(); + } else { + // The value is a file name and possibly a directory. + base::wcslcpy(filename, params.default_file_name.value().c_str(), + arraysize(filename)); + directory = params.default_file_name.DirName().value(); + } } + if (!directory.empty()) + ofn.lpstrInitialDir = directory.c_str(); std::wstring title; if (!params.title.empty()) @@ -375,16 +499,25 @@ bool RunSaveFileDialog(const content::FileChooserParams& params, // We use OFN_NOCHANGEDIR so that the user can rename or delete the directory // without having to close Chrome first. - ofn.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_ENABLESIZING | - OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; + ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_NOCHANGEDIR | + OFN_PATHMUSTEXIST; + if (params.hidereadonly) + ofn.Flags |= OFN_HIDEREADONLY; + if (params.overwriteprompt) + ofn.Flags |= OFN_OVERWRITEPROMPT; - std::wstring filter = GetFilterStringFromAcceptTypes(params.accept_types); - if (!filter.empty()) + const std::wstring& filter = GetFilterString(params.accept_types); + if (!filter.empty()) { ofn.lpstrFilter = filter.c_str(); + // Indices into |lpstrFilter| start at 1. + ofn.nFilterIndex = *filter_index + 1; + } bool success = !!GetSaveFileName(&ofn); - if (success) + if (success) { + *filter_index = ofn.nFilterIndex == 0 ? 0 : ofn.nFilterIndex - 1; *path = base::FilePath(filename); + } return success; } @@ -782,25 +915,32 @@ void CefBrowserHostImpl::PlatformHandleKeyboardEvent( } void CefBrowserHostImpl::PlatformRunFileChooser( - const content::FileChooserParams& params, + const FileChooserParams& params, RunFileChooserCallback callback) { + int filter_index = params.selected_accept_filter; std::vector files; + HWND owner = PlatformGetWindowHandle(); + if (params.mode == content::FileChooserParams::Open) { base::FilePath file; - if (RunOpenFileDialog(params, PlatformGetWindowHandle(), &file)) + if (RunOpenFileDialog(params, owner, &filter_index, &file)) files.push_back(file); } else if (params.mode == content::FileChooserParams::OpenMultiple) { - RunOpenMultiFileDialog(params, PlatformGetWindowHandle(), &files); + RunOpenMultiFileDialog(params, owner, &filter_index, &files); + } else if (params.mode == content::FileChooserParams::UploadFolder) { + base::FilePath file; + if (RunOpenFolderDialog(params, owner, &file)) + files.push_back(file); } else if (params.mode == content::FileChooserParams::Save) { base::FilePath file; - if (RunSaveFileDialog(params, PlatformGetWindowHandle(), &file)) + if (RunSaveFileDialog(params, owner, &filter_index, &file)) files.push_back(file); } else { NOTIMPLEMENTED(); } - callback.Run(files); + callback.Run(filter_index, files); } void CefBrowserHostImpl::PlatformHandleExternalProtocol(const GURL& url) { diff --git a/libcef/browser/download_manager_delegate.cc b/libcef/browser/download_manager_delegate.cc index 753b919d9..8a8656cd8 100644 --- a/libcef/browser/download_manager_delegate.cc +++ b/libcef/browser/download_manager_delegate.cc @@ -142,7 +142,7 @@ class CefBeforeDownloadCallbackImpl : public CefBeforeDownloadCallback { if (browser.get()) { handled = true; - content::FileChooserParams params; + CefBrowserHostImpl::FileChooserParams params; params.mode = content::FileChooserParams::Save; if (!suggested_path.empty()) { params.default_file_name = suggested_path; @@ -169,6 +169,7 @@ class CefBeforeDownloadCallbackImpl : public CefBeforeDownloadCallback { static void ChooseDownloadPathCallback( const content::DownloadTargetCallback& callback, + int selected_accept_filter, const std::vector& file_paths) { DCHECK_LE(file_paths.size(), (size_t) 1); diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index 560084242..d49cf73a7 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -246,29 +246,34 @@ void CEF_CALLBACK browser_host_set_zoom_level(struct _cef_browser_host_t* self, void CEF_CALLBACK browser_host_run_file_dialog(struct _cef_browser_host_t* self, cef_file_dialog_mode_t mode, const cef_string_t* title, - const cef_string_t* default_file_name, cef_string_list_t accept_types, - cef_run_file_dialog_callback_t* callback) { + const cef_string_t* default_file_path, cef_string_list_t accept_filters, + int selected_accept_filter, cef_run_file_dialog_callback_t* callback) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING DCHECK(self); if (!self) return; + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) + return; // Verify param: callback; type: refptr_diff DCHECK(callback); if (!callback) return; - // Unverified params: title, default_file_name, accept_types + // Unverified params: title, default_file_path, accept_filters - // Translate param: accept_types; type: string_vec_byref_const - std::vector accept_typesList; - transfer_string_list_contents(accept_types, accept_typesList); + // Translate param: accept_filters; type: string_vec_byref_const + std::vector accept_filtersList; + transfer_string_list_contents(accept_filters, accept_filtersList); // Execute CefBrowserHostCppToC::Get(self)->RunFileDialog( mode, CefString(title), - CefString(default_file_name), - accept_typesList, + CefString(default_file_path), + accept_filtersList, + selected_accept_filter, CefRunFileDialogCallbackCToCpp::Wrap(callback)); } diff --git a/libcef_dll/cpptoc/dialog_handler_cpptoc.cc b/libcef_dll/cpptoc/dialog_handler_cpptoc.cc index ec51e1c43..f5b2aa374 100644 --- a/libcef_dll/cpptoc/dialog_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/dialog_handler_cpptoc.cc @@ -21,8 +21,8 @@ int CEF_CALLBACK dialog_handler_on_file_dialog( struct _cef_dialog_handler_t* self, cef_browser_t* browser, cef_file_dialog_mode_t mode, const cef_string_t* title, - const cef_string_t* default_file_name, cef_string_list_t accept_types, - cef_file_dialog_callback_t* callback) { + const cef_string_t* default_file_path, cef_string_list_t accept_filters, + int selected_accept_filter, cef_file_dialog_callback_t* callback) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING DCHECK(self); @@ -32,23 +32,28 @@ int CEF_CALLBACK dialog_handler_on_file_dialog( DCHECK(browser); if (!browser) return 0; + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) + return 0; // Verify param: callback; type: refptr_diff DCHECK(callback); if (!callback) return 0; - // Unverified params: title, default_file_name, accept_types + // Unverified params: title, default_file_path, accept_filters - // Translate param: accept_types; type: string_vec_byref_const - std::vector accept_typesList; - transfer_string_list_contents(accept_types, accept_typesList); + // Translate param: accept_filters; type: string_vec_byref_const + std::vector accept_filtersList; + transfer_string_list_contents(accept_filters, accept_filtersList); // Execute bool _retval = CefDialogHandlerCppToC::Get(self)->OnFileDialog( CefBrowserCToCpp::Wrap(browser), mode, CefString(title), - CefString(default_file_name), - accept_typesList, + CefString(default_file_path), + accept_filtersList, + selected_accept_filter, CefFileDialogCallbackCToCpp::Wrap(callback)); // Return type: bool diff --git a/libcef_dll/cpptoc/file_dialog_callback_cpptoc.cc b/libcef_dll/cpptoc/file_dialog_callback_cpptoc.cc index e42c975f0..40f7b1562 100644 --- a/libcef_dll/cpptoc/file_dialog_callback_cpptoc.cc +++ b/libcef_dll/cpptoc/file_dialog_callback_cpptoc.cc @@ -17,16 +17,18 @@ // MEMBER FUNCTIONS - Body may be edited by hand. void CEF_CALLBACK file_dialog_callback_cont( - struct _cef_file_dialog_callback_t* self, cef_string_list_t file_paths) { + struct _cef_file_dialog_callback_t* self, int selected_accept_filter, + cef_string_list_t file_paths) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING DCHECK(self); if (!self) return; - // Verify param: file_paths; type: string_vec_byref_const - DCHECK(file_paths); - if (!file_paths) + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) return; + // Unverified params: file_paths // Translate param: file_paths; type: string_vec_byref_const std::vector file_pathsList; @@ -34,6 +36,7 @@ void CEF_CALLBACK file_dialog_callback_cont( // Execute CefFileDialogCallbackCppToC::Get(self)->Continue( + selected_accept_filter, file_pathsList); } diff --git a/libcef_dll/cpptoc/run_file_dialog_callback_cpptoc.cc b/libcef_dll/cpptoc/run_file_dialog_callback_cpptoc.cc index 93a03ea30..d668d3a87 100644 --- a/libcef_dll/cpptoc/run_file_dialog_callback_cpptoc.cc +++ b/libcef_dll/cpptoc/run_file_dialog_callback_cpptoc.cc @@ -11,28 +11,24 @@ // #include "libcef_dll/cpptoc/run_file_dialog_callback_cpptoc.h" -#include "libcef_dll/ctocpp/browser_host_ctocpp.h" #include "libcef_dll/transfer_util.h" // MEMBER FUNCTIONS - Body may be edited by hand. -void CEF_CALLBACK run_file_dialog_callback_cont( - struct _cef_run_file_dialog_callback_t* self, - struct _cef_browser_host_t* browser_host, cef_string_list_t file_paths) { +void CEF_CALLBACK run_file_dialog_callback_on_file_dialog_dismissed( + struct _cef_run_file_dialog_callback_t* self, int selected_accept_filter, + cef_string_list_t file_paths) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING DCHECK(self); if (!self) return; - // Verify param: browser_host; type: refptr_diff - DCHECK(browser_host); - if (!browser_host) - return; - // Verify param: file_paths; type: string_vec_byref_const - DCHECK(file_paths); - if (!file_paths) + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) return; + // Unverified params: file_paths // Translate param: file_paths; type: string_vec_byref_const std::vector file_pathsList; @@ -40,7 +36,7 @@ void CEF_CALLBACK run_file_dialog_callback_cont( // Execute CefRunFileDialogCallbackCppToC::Get(self)->OnFileDialogDismissed( - CefBrowserHostCToCpp::Wrap(browser_host), + selected_accept_filter, file_pathsList); } @@ -51,7 +47,8 @@ CefRunFileDialogCallbackCppToC::CefRunFileDialogCallbackCppToC( CefRunFileDialogCallback* cls) : CefCppToC(cls) { - struct_.struct_.cont = run_file_dialog_callback_cont; + struct_.struct_.on_file_dialog_dismissed = + run_file_dialog_callback_on_file_dialog_dismissed; } #ifndef NDEBUG diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.cc b/libcef_dll/ctocpp/browser_host_ctocpp.cc index 722224f06..1d6b220e3 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -188,37 +188,42 @@ void CefBrowserHostCToCpp::SetZoomLevel(double zoomLevel) { } void CefBrowserHostCToCpp::RunFileDialog(FileDialogMode mode, - const CefString& title, const CefString& default_file_name, - const std::vector& accept_types, + const CefString& title, const CefString& default_file_path, + const std::vector& accept_filters, int selected_accept_filter, CefRefPtr callback) { if (CEF_MEMBER_MISSING(struct_, run_file_dialog)) return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) + return; // Verify param: callback; type: refptr_diff DCHECK(callback.get()); if (!callback.get()) return; - // Unverified params: title, default_file_name, accept_types + // Unverified params: title, default_file_path, accept_filters - // Translate param: accept_types; type: string_vec_byref_const - cef_string_list_t accept_typesList = cef_string_list_alloc(); - DCHECK(accept_typesList); - if (accept_typesList) - transfer_string_list_contents(accept_types, accept_typesList); + // Translate param: accept_filters; type: string_vec_byref_const + cef_string_list_t accept_filtersList = cef_string_list_alloc(); + DCHECK(accept_filtersList); + if (accept_filtersList) + transfer_string_list_contents(accept_filters, accept_filtersList); // Execute struct_->run_file_dialog(struct_, mode, title.GetStruct(), - default_file_name.GetStruct(), - accept_typesList, + default_file_path.GetStruct(), + accept_filtersList, + selected_accept_filter, CefRunFileDialogCallbackCppToC::Wrap(callback)); - // Restore param:accept_types; type: string_vec_byref_const - if (accept_typesList) - cef_string_list_free(accept_typesList); + // Restore param:accept_filters; type: string_vec_byref_const + if (accept_filtersList) + cef_string_list_free(accept_filtersList); } void CefBrowserHostCToCpp::StartDownload(const CefString& url) { diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index 7992d8ba6..9c5290ee3 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -47,8 +47,8 @@ class CefBrowserHostCToCpp virtual double GetZoomLevel() OVERRIDE; virtual void SetZoomLevel(double zoomLevel) OVERRIDE; virtual void RunFileDialog(FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, int selected_accept_filter, CefRefPtr callback) OVERRIDE; virtual void StartDownload(const CefString& url) OVERRIDE; virtual void Print() OVERRIDE; diff --git a/libcef_dll/ctocpp/dialog_handler_ctocpp.cc b/libcef_dll/ctocpp/dialog_handler_ctocpp.cc index ae889d8ac..d2a522dcd 100644 --- a/libcef_dll/ctocpp/dialog_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/dialog_handler_ctocpp.cc @@ -20,8 +20,8 @@ bool CefDialogHandlerCToCpp::OnFileDialog(CefRefPtr browser, FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, int selected_accept_filter, CefRefPtr callback) { if (CEF_MEMBER_MISSING(struct_, on_file_dialog)) return false; @@ -32,30 +32,35 @@ bool CefDialogHandlerCToCpp::OnFileDialog(CefRefPtr browser, DCHECK(browser.get()); if (!browser.get()) return false; + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) + return false; // Verify param: callback; type: refptr_diff DCHECK(callback.get()); if (!callback.get()) return false; - // Unverified params: title, default_file_name, accept_types + // Unverified params: title, default_file_path, accept_filters - // Translate param: accept_types; type: string_vec_byref_const - cef_string_list_t accept_typesList = cef_string_list_alloc(); - DCHECK(accept_typesList); - if (accept_typesList) - transfer_string_list_contents(accept_types, accept_typesList); + // Translate param: accept_filters; type: string_vec_byref_const + cef_string_list_t accept_filtersList = cef_string_list_alloc(); + DCHECK(accept_filtersList); + if (accept_filtersList) + transfer_string_list_contents(accept_filters, accept_filtersList); // Execute int _retval = struct_->on_file_dialog(struct_, CefBrowserCppToC::Wrap(browser), mode, title.GetStruct(), - default_file_name.GetStruct(), - accept_typesList, + default_file_path.GetStruct(), + accept_filtersList, + selected_accept_filter, CefFileDialogCallbackCppToC::Wrap(callback)); - // Restore param:accept_types; type: string_vec_byref_const - if (accept_typesList) - cef_string_list_free(accept_typesList); + // Restore param:accept_filters; type: string_vec_byref_const + if (accept_filtersList) + cef_string_list_free(accept_filtersList); // Return type: bool return _retval?true:false; diff --git a/libcef_dll/ctocpp/dialog_handler_ctocpp.h b/libcef_dll/ctocpp/dialog_handler_ctocpp.h index 45ce37f39..665add1e6 100644 --- a/libcef_dll/ctocpp/dialog_handler_ctocpp.h +++ b/libcef_dll/ctocpp/dialog_handler_ctocpp.h @@ -35,8 +35,8 @@ class CefDialogHandlerCToCpp // CefDialogHandler methods bool OnFileDialog(CefRefPtr browser, FileDialogMode mode, - const CefString& title, const CefString& default_file_name, - const std::vector& accept_types, + const CefString& title, const CefString& default_file_path, + const std::vector& accept_filters, int selected_accept_filter, CefRefPtr callback) override; }; diff --git a/libcef_dll/ctocpp/file_dialog_callback_ctocpp.cc b/libcef_dll/ctocpp/file_dialog_callback_ctocpp.cc index 2d0ff3026..df8324c5d 100644 --- a/libcef_dll/ctocpp/file_dialog_callback_ctocpp.cc +++ b/libcef_dll/ctocpp/file_dialog_callback_ctocpp.cc @@ -16,13 +16,19 @@ // VIRTUAL METHODS - Body may be edited by hand. -void CefFileDialogCallbackCToCpp::Continue( +void CefFileDialogCallbackCToCpp::Continue(int selected_accept_filter, const std::vector& file_paths) { if (CEF_MEMBER_MISSING(struct_, cont)) return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) + return; + // Unverified params: file_paths + // Translate param: file_paths; type: string_vec_byref_const cef_string_list_t file_pathsList = cef_string_list_alloc(); DCHECK(file_pathsList); @@ -31,6 +37,7 @@ void CefFileDialogCallbackCToCpp::Continue( // Execute struct_->cont(struct_, + selected_accept_filter, file_pathsList); // Restore param:file_paths; type: string_vec_byref_const diff --git a/libcef_dll/ctocpp/file_dialog_callback_ctocpp.h b/libcef_dll/ctocpp/file_dialog_callback_ctocpp.h index 5d8a4ad1b..92a329e41 100644 --- a/libcef_dll/ctocpp/file_dialog_callback_ctocpp.h +++ b/libcef_dll/ctocpp/file_dialog_callback_ctocpp.h @@ -34,7 +34,8 @@ class CefFileDialogCallbackCToCpp cef_file_dialog_callback_t>(str) {} // CefFileDialogCallback methods - virtual void Continue(const std::vector& file_paths) OVERRIDE; + virtual void Continue(int selected_accept_filter, + const std::vector& file_paths) OVERRIDE; virtual void Cancel() OVERRIDE; }; diff --git a/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.cc b/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.cc index d254589d3..ce7a70f38 100644 --- a/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.cc +++ b/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.cc @@ -10,7 +10,6 @@ // for more information. // -#include "libcef_dll/cpptoc/browser_host_cpptoc.h" #include "libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.h" #include "libcef_dll/transfer_util.h" @@ -18,17 +17,17 @@ // VIRTUAL METHODS - Body may be edited by hand. void CefRunFileDialogCallbackCToCpp::OnFileDialogDismissed( - CefRefPtr browser_host, - const std::vector& file_paths) { - if (CEF_MEMBER_MISSING(struct_, cont)) + int selected_accept_filter, const std::vector& file_paths) { + if (CEF_MEMBER_MISSING(struct_, on_file_dialog_dismissed)) return; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING - // Verify param: browser_host; type: refptr_diff - DCHECK(browser_host.get()); - if (!browser_host.get()) + // Verify param: selected_accept_filter; type: simple_byval + DCHECK_GE(selected_accept_filter, 0); + if (selected_accept_filter < 0) return; + // Unverified params: file_paths // Translate param: file_paths; type: string_vec_byref_const cef_string_list_t file_pathsList = cef_string_list_alloc(); @@ -37,8 +36,8 @@ void CefRunFileDialogCallbackCToCpp::OnFileDialogDismissed( transfer_string_list_contents(file_paths, file_pathsList); // Execute - struct_->cont(struct_, - CefBrowserHostCppToC::Wrap(browser_host), + struct_->on_file_dialog_dismissed(struct_, + selected_accept_filter, file_pathsList); // Restore param:file_paths; type: string_vec_byref_const diff --git a/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.h b/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.h index 29e49cb3a..af1300cfa 100644 --- a/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.h +++ b/libcef_dll/ctocpp/run_file_dialog_callback_ctocpp.h @@ -36,7 +36,7 @@ class CefRunFileDialogCallbackCToCpp cef_run_file_dialog_callback_t>(str) {} // CefRunFileDialogCallback methods - void OnFileDialogDismissed(CefRefPtr browser_host, + void OnFileDialogDismissed(int selected_accept_filter, const std::vector& file_paths) override; }; diff --git a/tests/cefclient/client_handler.cpp b/tests/cefclient/client_handler.cpp index 92de2a1ac..deeb81e0c 100644 --- a/tests/cefclient/client_handler.cpp +++ b/tests/cefclient/client_handler.cpp @@ -195,8 +195,9 @@ bool ClientHandler::OnContextMenuCommand( bool ClientHandler::OnFileDialog(CefRefPtr browser, FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) { CEF_REQUIRE_UI_THREAD(); @@ -841,12 +842,16 @@ void ClientHandler::EndTracing() { // Results in a call to OnFileDialogDismissed. handler_->GetBrowser()->GetHost()->RunFileDialog( - FILE_DIALOG_SAVE, CefString(), path, std::vector(), + FILE_DIALOG_SAVE, + CefString(), // title + path, + std::vector(), // accept_filters + 0, // selected_accept_filter this); } virtual void OnFileDialogDismissed( - CefRefPtr browser_host, + int selected_accept_filter, const std::vector& file_paths) OVERRIDE { CEF_REQUIRE_UI_THREAD(); if (!file_paths.empty()) { diff --git a/tests/cefclient/client_handler.h b/tests/cefclient/client_handler.h index effdc87d1..a57bf64c3 100644 --- a/tests/cefclient/client_handler.h +++ b/tests/cefclient/client_handler.h @@ -111,8 +111,9 @@ class ClientHandler : public CefClient, virtual bool OnFileDialog(CefRefPtr browser, FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) OVERRIDE; // CefDisplayHandler methods diff --git a/tests/cefclient/client_handler_gtk.cpp b/tests/cefclient/client_handler_gtk.cpp index 9772bbf5b..5f907ed71 100644 --- a/tests/cefclient/client_handler_gtk.cpp +++ b/tests/cefclient/client_handler_gtk.cpp @@ -50,52 +50,74 @@ std::string GetDescriptionFromMimeType(const std::string& mime_type) { return std::string(); } -void AddFiltersForAcceptTypes(GtkFileChooser* chooser, - const std::vector& accept_types, - bool include_all_files) { +void AddFilters(GtkFileChooser* chooser, + const std::vector& accept_filters, + bool include_all_files, + std::vector* filters) { bool has_filter = false; - for (size_t i = 0; i < accept_types.size(); ++i) { - std::string ascii_type = accept_types[i]; - if (ascii_type.length()) { - // Just treat as extension if contains '.' as the first character. - if (ascii_type[0] == '.') { - GtkFileFilter* filter = gtk_file_filter_new(); - std::string pattern = "*" + ascii_type; - gtk_file_filter_add_pattern(filter, pattern.c_str()); - gtk_file_filter_set_name(filter, pattern.c_str()); - gtk_file_chooser_add_filter(chooser, filter); - if (!has_filter) - has_filter = true; - } else { - // Otherwise convert mime type to one or more extensions. - GtkFileFilter* filter = NULL; - std::string description = GetDescriptionFromMimeType(ascii_type); - bool description_from_ext = description.empty(); + for (size_t i = 0; i < accept_filters.size(); ++i) { + const std::string& filter = accept_filters[i]; + if (filter.empty()) + continue; - std::vector ext; - CefGetExtensionsForMimeType(ascii_type, ext); - for (size_t x = 0; x < ext.size(); ++x) { - if (!filter) - filter = gtk_file_filter_new(); - std::string pattern = "*." + ext[x].ToString(); - gtk_file_filter_add_pattern(filter, pattern.c_str()); + std::vector extensions; + std::string description; - if (description_from_ext) { - if (x != 0) - description += ";"; - description += pattern; - } - } + size_t sep_index = filter.find('|'); + if (sep_index != std::string::npos) { + // Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3". + description = filter.substr(0, sep_index); - if (filter) { - gtk_file_filter_set_name(filter, description.c_str()); - gtk_file_chooser_add_filter(chooser, filter); - if (!has_filter) - has_filter = true; + const std::string& exts = filter.substr(sep_index + 1); + size_t last = 0; + size_t size = exts.size(); + for (size_t i = 0; i <= size; ++i) { + if (i == size || exts[i] == ';') { + std::string ext(exts, last, i - last); + if (!ext.empty() && ext[0] == '.') + extensions.push_back(ext); + last = i + 1; } } + } else if (filter[0] == '.') { + // Treat as an extension beginning with the '.' character. + extensions.push_back(filter); + } else { + // Otherwise convert mime type to one or more extensions. + description = GetDescriptionFromMimeType(filter); + + std::vector ext; + CefGetExtensionsForMimeType(filter, ext); + for (size_t x = 0; x < ext.size(); ++x) + extensions.push_back("." + ext[x].ToString()); } + + if (extensions.empty()) + continue; + + GtkFileFilter* gtk_filter = gtk_file_filter_new(); + + std::string ext_str; + for (size_t x = 0; x < extensions.size(); ++x) { + const std::string& pattern = "*" + extensions[x]; + if (x != 0) + ext_str += ";"; + ext_str += pattern; + gtk_file_filter_add_pattern(gtk_filter, pattern.c_str()); + } + + if (description.empty()) + description = ext_str; + else + description += " (" + ext_str + ")"; + + gtk_file_filter_set_name(gtk_filter, description.c_str()); + gtk_file_chooser_add_filter(chooser, gtk_filter); + if (!has_filter) + has_filter = true; + + filters->push_back(gtk_filter); } // Add the *.* filter, but only if we have added other filters (otherwise it @@ -103,7 +125,7 @@ void AddFiltersForAcceptTypes(GtkFileChooser* chooser, if (include_all_files && has_filter) { GtkFileFilter* filter = gtk_file_filter_new(); gtk_file_filter_add_pattern(filter, "*"); - gtk_file_filter_set_name(filter, "All Files"); + gtk_file_filter_set_name(filter, "All Files (*)"); gtk_file_chooser_add_filter(chooser, filter); } } @@ -113,17 +135,26 @@ void AddFiltersForAcceptTypes(GtkFileChooser* chooser, bool ClientHandler::OnFileDialog(CefRefPtr browser, FileDialogMode mode, const CefString& title, - const CefString& default_file_name, - const std::vector& accept_types, + const CefString& default_file_path, + const std::vector& accept_filters, + int selected_accept_filter, CefRefPtr callback) { std::vector files; GtkFileChooserAction action; const gchar* accept_button; - if (mode == FILE_DIALOG_OPEN || mode == FILE_DIALOG_OPEN_MULTIPLE) { + + // Remove any modifier flags. + FileDialogMode mode_type = + static_cast(mode & FILE_DIALOG_TYPE_MASK); + + if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) { action = GTK_FILE_CHOOSER_ACTION_OPEN; accept_button = GTK_STOCK_OPEN; - } else if (mode == FILE_DIALOG_SAVE) { + } else if (mode_type == FILE_DIALOG_OPEN_FOLDER) { + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + accept_button = GTK_STOCK_OPEN; + } else if (mode_type == FILE_DIALOG_SAVE) { action = GTK_FILE_CHOOSER_ACTION_SAVE; accept_button = GTK_STOCK_SAVE; } else { @@ -131,12 +162,6 @@ bool ClientHandler::OnFileDialog(CefRefPtr browser, return false; } - std::string base_name; - if (!default_file_name.empty()) { - base_name = - basename(const_cast(default_file_name.ToString().c_str())); - } - std::string title_str; if (!title.empty()) { title_str = title; @@ -148,6 +173,9 @@ bool ClientHandler::OnFileDialog(CefRefPtr browser, case FILE_DIALOG_OPEN_MULTIPLE: title_str = "Open Files"; break; + case FILE_DIALOG_OPEN_FOLDER: + title_str = "Open Folder"; + break; case FILE_DIALOG_SAVE: title_str = "Save File"; break; @@ -166,28 +194,39 @@ bool ClientHandler::OnFileDialog(CefRefPtr browser, accept_button, GTK_RESPONSE_ACCEPT, NULL); - if (mode == FILE_DIALOG_OPEN_MULTIPLE) { + if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); - } else if (mode == FILE_DIALOG_SAVE) { - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), - TRUE); + + if (mode_type == FILE_DIALOG_SAVE) { + gtk_file_chooser_set_do_overwrite_confirmation( + GTK_FILE_CHOOSER(dialog), + !!(mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG)); } - if (mode == FILE_DIALOG_SAVE && !base_name.empty()) { - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), - base_name.c_str()); + gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), + !(mode & FILE_DIALOG_HIDEREADONLY_FLAG)); + + if (!default_file_path.empty()) { + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), + default_file_path.ToString().c_str()); } - AddFiltersForAcceptTypes(GTK_FILE_CHOOSER(dialog), accept_types, true); + std::vector filters; + AddFilters(GTK_FILE_CHOOSER(dialog), accept_filters, true, &filters); + if (selected_accept_filter < static_cast(filters.size())) { + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), + filters[selected_accept_filter]); + } bool success = false; if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - if (mode == FILE_DIALOG_OPEN || mode == FILE_DIALOG_SAVE) { + if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_FOLDER || + mode_type == FILE_DIALOG_SAVE) { char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); files.push_back(std::string(filename)); success = true; - } else if (mode == FILE_DIALOG_OPEN_MULTIPLE) { + } else if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) { GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); if (filenames) { @@ -203,10 +242,24 @@ bool ClientHandler::OnFileDialog(CefRefPtr browser, } } + int filter_index = selected_accept_filter; + if (success) { + GtkFileFilter* selected_filter = + gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); + if (selected_filter != NULL) { + for (size_t x = 0; x < filters.size(); ++x) { + if (filters[x] == selected_filter) { + filter_index = x; + break; + } + } + } + } + gtk_widget_destroy(dialog); if (success) - callback->Continue(files); + callback->Continue(filter_index, files); else callback->Cancel(); diff --git a/tests/cefclient/dialog_test.cpp b/tests/cefclient/dialog_test.cpp index 723c74172..96cea9067 100644 --- a/tests/cefclient/dialog_test.cpp +++ b/tests/cefclient/dialog_test.cpp @@ -14,19 +14,63 @@ namespace { const char kTestUrl[] = "http://tests/dialogs"; const char kFileOpenMessageName[] = "DialogTest.FileOpen"; const char kFileOpenMultipleMessageName[] = "DialogTest.FileOpenMultiple"; +const char kFileOpenFolderMessageName[] = "DialogTest.FileOpenFolder"; const char kFileSaveMessageName[] = "DialogTest.FileSave"; +#if defined(OS_WIN) +#define PATH_SEP '\\' +#else +#define PATH_SEP '/' +#endif + +// Store persistent dialog state information. +class DialogState : public base::RefCountedThreadSafe { + public: + DialogState() + : mode_(FILE_DIALOG_OPEN), + last_selected_filter_(0), + pending_(false) {} + + cef_file_dialog_mode_t mode_; + int last_selected_filter_; + CefString last_file_; + bool pending_; + + DISALLOW_COPY_AND_ASSIGN(DialogState); +}; + // Callback executed when the file dialog is dismissed. class DialogCallback : public CefRunFileDialogCallback { public: - explicit DialogCallback( - CefRefPtr router_callback) - : router_callback_(router_callback) { + DialogCallback( + CefRefPtr router_callback, + scoped_refptr dialog_state) + : router_callback_(router_callback), + dialog_state_(dialog_state) { } virtual void OnFileDialogDismissed( - CefRefPtr browser_host, + int last_selected_filter, const std::vector& file_paths) OVERRIDE { + CEF_REQUIRE_UI_THREAD(); + DCHECK(dialog_state_->pending_); + + if (!file_paths.empty()) { + if (dialog_state_->mode_ != FILE_DIALOG_OPEN_FOLDER) + dialog_state_->last_selected_filter_ = last_selected_filter; + + dialog_state_->last_file_ = file_paths[0]; + if (dialog_state_->mode_ == FILE_DIALOG_OPEN_FOLDER) { + std::string last_file = dialog_state_->last_file_; + if (last_file[last_file.length() - 1] != PATH_SEP) { + // Add a trailing slash so we know it's a directory. Otherwise, file + // dialogs will think the last path component is a file name. + last_file += PATH_SEP; + dialog_state_->last_file_ = last_file; + } + } + } + // Send a message back to the render process with the list of file paths. std::string response; for (int i = 0; i < static_cast(file_paths.size()); ++i) { @@ -37,12 +81,17 @@ class DialogCallback : public CefRunFileDialogCallback { router_callback_->Success(response); router_callback_ = NULL; + + dialog_state_->pending_ = false; + dialog_state_ = NULL; } private: CefRefPtr router_callback_; + scoped_refptr dialog_state_; IMPLEMENT_REFCOUNTING(DialogCallback); + DISALLOW_COPY_AND_ASSIGN(DialogCallback); }; // Handle messages in the browser process. @@ -57,36 +106,72 @@ class Handler : public CefMessageRouterBrowserSide::Handler { const CefString& request, bool persistent, CefRefPtr callback) OVERRIDE { + CEF_REQUIRE_UI_THREAD(); + // Only handle messages from the test URL. const std::string& url = frame->GetURL(); if (url.find(kTestUrl) != 0) return false; - // Sample file type filter. - std::vector file_types; - file_types.push_back("text/*"); - file_types.push_back(".log"); - file_types.push_back(".patch"); + if (!dialog_state_.get()) + dialog_state_ = new DialogState; - CefRefPtr dialog_callback(new DialogCallback(callback)); + // Make sure we're only running one dialog at a time. + DCHECK(!dialog_state_->pending_); + + std::vector accept_filters; + std::string title; const std::string& message_name = request; if (message_name == kFileOpenMessageName) { - browser->GetHost()->RunFileDialog(FILE_DIALOG_OPEN, "My Open Dialog", - "test.txt", file_types, dialog_callback.get()); + dialog_state_->mode_ = FILE_DIALOG_OPEN; + title = "My Open Dialog"; } else if (message_name == kFileOpenMultipleMessageName) { - browser->GetHost()->RunFileDialog(FILE_DIALOG_OPEN_MULTIPLE, - "My Open Multiple Dialog", CefString(), file_types, - dialog_callback.get()); + dialog_state_->mode_ = FILE_DIALOG_OPEN_MULTIPLE; + title = "My Open Multiple Dialog"; + } else if (message_name == kFileOpenFolderMessageName) { + dialog_state_->mode_ = FILE_DIALOG_OPEN_FOLDER; + title = "My Open Folder Dialog"; } else if (message_name == kFileSaveMessageName) { - browser->GetHost()->RunFileDialog(FILE_DIALOG_SAVE, "My Save Dialog", - "test.txt", file_types, dialog_callback.get()); + dialog_state_->mode_ = static_cast( + FILE_DIALOG_SAVE | FILE_DIALOG_OVERWRITEPROMPT_FLAG); + title = "My Save Dialog"; } else { NOTREACHED(); + return true; } + if (dialog_state_->mode_ != FILE_DIALOG_OPEN_FOLDER) { + // Build filters based on mime time. + accept_filters.push_back("text/*"); + + // Build filters based on file extension. + accept_filters.push_back(".log"); + accept_filters.push_back(".patch"); + + // Add specific filters as-is. + accept_filters.push_back("Document Files|.doc;.odt"); + accept_filters.push_back("Image Files|.png;.jpg;.gif"); + accept_filters.push_back("PDF Files|.pdf"); + } + + dialog_state_->pending_ = true; + + browser->GetHost()->RunFileDialog( + dialog_state_->mode_, + title, + dialog_state_->last_file_, + accept_filters, + dialog_state_->last_selected_filter_, + new DialogCallback(callback, dialog_state_)); + return true; } + + private: + scoped_refptr dialog_state_; + + DISALLOW_COPY_AND_ASSIGN(Handler); }; } // namespace diff --git a/tests/cefclient/res/dialogs.html b/tests/cefclient/res/dialogs.html index becec40e5..9d1671ac8 100644 --- a/tests/cefclient/res/dialogs.html +++ b/tests/cefclient/res/dialogs.html @@ -57,6 +57,7 @@ Click a button to show the associated dialog type.
input type="file":

+

diff --git a/tests/unittests/dialog_unittest.cc b/tests/unittests/dialog_unittest.cc index 9050d38ba..36ea45082 100644 --- a/tests/unittests/dialog_unittest.cc +++ b/tests/unittests/dialog_unittest.cc @@ -22,6 +22,7 @@ class DialogTestHandler : public TestHandler { : mode(dialog_mode), title("Test Title"), default_file_name("Test File Name"), + selected_accept_filter(1), // Something other than 0 for testing. callback_async(false), callback_cancel(false) { accept_types.push_back("text/*"); @@ -33,6 +34,7 @@ class DialogTestHandler : public TestHandler { CefString title; CefString default_file_name; std::vector accept_types; + int selected_accept_filter; bool callback_async; // True if the callback should execute asynchronously. bool callback_cancel; // True if the callback should cancel. @@ -46,17 +48,18 @@ class DialogTestHandler : public TestHandler { } void OnFileDialogDismissed( - CefRefPtr browser_host, + int selected_accept_filter, const std::vector& file_paths) override { handler_->got_onfiledialogdismissed_.yes(); - std::string url = browser_host->GetBrowser()->GetMainFrame()->GetURL(); - EXPECT_STREQ(kTestUrl, url.c_str()); - - if (handler_->config_.callback_cancel) + if (handler_->config_.callback_cancel) { + EXPECT_EQ(0, selected_accept_filter); EXPECT_TRUE(file_paths.empty()); - else + } else { + EXPECT_EQ(handler_->config_.selected_accept_filter, + selected_accept_filter); TestStringVectorEqual(handler_->config_.callback_paths, file_paths); + } handler_->DestroyTest(); handler_ = NULL; @@ -89,6 +92,7 @@ class DialogTestHandler : public TestHandler { config_.title, config_.default_file_name, config_.accept_types, + config_.selected_accept_filter, new Callback(this)); } @@ -96,7 +100,8 @@ class DialogTestHandler : public TestHandler { if (config_.callback_cancel) callback->Cancel(); else - callback->Continue(config_.callback_paths); + callback->Continue(config_.selected_accept_filter, + config_.callback_paths); } // CefDialogHandler @@ -106,6 +111,7 @@ class DialogTestHandler : public TestHandler { const CefString& title, const CefString& default_file_name, const std::vector& accept_types, + int selected_accept_filter, CefRefPtr callback) override { got_onfiledialog_.yes(); @@ -157,6 +163,22 @@ TEST(DialogTest, FileEmptyParams) { ReleaseAndWaitForDestructor(handler); } +TEST(DialogTest, FileAdditionalFlags) { + DialogTestHandler::TestConfig config( + static_cast(FILE_DIALOG_OPEN | + FILE_DIALOG_HIDEREADONLY_FLAG | + FILE_DIALOG_OVERWRITEPROMPT_FLAG)); + config.title.clear(); + config.default_file_name.clear(); + config.accept_types.clear(); + config.callback_async = false; + config.callback_cancel = false; + + CefRefPtr handler = new DialogTestHandler(config); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + TEST(DialogTest, FileOpen) { DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN); config.callback_async = false; @@ -243,6 +265,48 @@ TEST(DialogTest, FileOpenMultipleAsyncCancel) { ReleaseAndWaitForDestructor(handler); } +TEST(DialogTest, FileOpenFolder) { + DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN_FOLDER); + config.callback_async = false; + config.callback_cancel = false; + config.callback_paths.push_back("/path/to/folder"); + + CefRefPtr handler = new DialogTestHandler(config); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +TEST(DialogTest, FileOpenFolderCancel) { + DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN_FOLDER); + config.callback_async = false; + config.callback_cancel = true; + + CefRefPtr handler = new DialogTestHandler(config); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +TEST(DialogTest, FileOpenFolderAsync) { + DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN_FOLDER); + config.callback_async = true; + config.callback_cancel = false; + config.callback_paths.push_back("/path/to/folder"); + + CefRefPtr handler = new DialogTestHandler(config); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +TEST(DialogTest, FileOpenFolderAsyncCancel) { + DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN_FOLDER); + config.callback_async = false; + config.callback_cancel = true; + + CefRefPtr handler = new DialogTestHandler(config); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + TEST(DialogTest, FileSave) { DialogTestHandler::TestConfig config(FILE_DIALOG_SAVE); config.callback_async = false;