Pass mime type values as file dialog accept filters (see #3314)
File dialogs that specify mime type (e.g. "image/*") accept filters will pass
those values unchanged to the OnFileDialog |accept_filters| parameter. The
default dialog implementation will show those filters in addition to a combined
"Custom Files" filter. This is a change from preexisting Google Chrome
behavior where only the combined "Custom Files" filter is displayed, and
restores CEF behavior that existed prior to 2ea7459a89
.
Document the fact that OnFileDialog may be called twice, once before MIME type
expansion and once afterwards.
Add new OnFileDialog |accept_extensions| and |accept_descriptions| parameters
for MIME type extensions and descriptions.
Details: This change adds a SelectFileDialog::FileTypeInfo::extension_mimetypes
member and improves the logic in FileSelectHelper::GetFileTypesFromAcceptType
and file_dialog_manager.cc SelectFileToFileChooserParams to support recall of
the source mime type when populating the FileChooserParams structure.
To test:
- Run `ceftests --gtest_filter=DialogTest.*`
- Run `cefclient --url=https://tests/dialogs`
This commit is contained in:
parent
b0bceecba9
commit
8e79307a62
|
@ -33,7 +33,7 @@
|
||||||
// by hand. See the translator.README.txt file in the tools directory for
|
// by hand. See the translator.README.txt file in the tools directory for
|
||||||
// more information.
|
// more information.
|
||||||
//
|
//
|
||||||
// $hash=5644fdc2453dd083079bf9e3616b687eeb49f250$
|
// $hash=bf7208a86ee17f63fd7163cef8c3a13373a1f1c8$
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef CEF_INCLUDE_CAPI_CEF_DIALOG_HANDLER_CAPI_H_
|
#ifndef CEF_INCLUDE_CAPI_CEF_DIALOG_HANDLER_CAPI_H_
|
||||||
|
@ -86,13 +86,20 @@ typedef struct _cef_dialog_handler_t {
|
||||||
/// to show the default title ("Open" or "Save" depending on the mode).
|
/// to show the default title ("Open" or "Save" depending on the mode).
|
||||||
/// |default_file_path| is the path with optional directory and/or file name
|
/// |default_file_path| is the path with optional directory and/or file name
|
||||||
/// component that should be initially selected in the dialog.
|
/// component that should be initially selected in the dialog.
|
||||||
/// |accept_filters| are used to restrict the selectable file types and may
|
/// |accept_filters| are used to restrict the selectable file types and may be
|
||||||
/// any combination of (a) valid lower-cased MIME types (e.g. "text/*" or
|
/// any combination of valid lower-cased MIME types (e.g. "text/*" or
|
||||||
/// "image/*"), (b) individual file extensions (e.g. ".txt" or ".png"), or (c)
|
/// "image/*") and individual file extensions (e.g. ".txt" or ".png").
|
||||||
/// combined description and file extension delimited using "|" and ";" (e.g.
|
/// |accept_extensions| provides the semicolon-delimited expansion of MIME
|
||||||
/// "Image Types|.png;.gif;.jpg"). To display a custom dialog return true (1)
|
/// types to file extensions (if known, or NULL string otherwise).
|
||||||
/// and execute |callback| either inline or at a later time. To display the
|
/// |accept_descriptions| provides the descriptions for MIME types (if known,
|
||||||
/// default dialog return false (0).
|
/// or NULL string otherwise). For example, the "image/*" mime type might have
|
||||||
|
/// extensions ".png;.jpg;.bmp;..." and description "Image Files".
|
||||||
|
/// |accept_filters|, |accept_extensions| and |accept_descriptions| will all
|
||||||
|
/// be the same size. 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). If this function returns false (0) it may be called an
|
||||||
|
/// additional time for the same dialog (both before and after MIME type
|
||||||
|
/// expansion).
|
||||||
///
|
///
|
||||||
int(CEF_CALLBACK* on_file_dialog)(
|
int(CEF_CALLBACK* on_file_dialog)(
|
||||||
struct _cef_dialog_handler_t* self,
|
struct _cef_dialog_handler_t* self,
|
||||||
|
@ -101,6 +108,8 @@ typedef struct _cef_dialog_handler_t {
|
||||||
const cef_string_t* title,
|
const cef_string_t* title,
|
||||||
const cef_string_t* default_file_path,
|
const cef_string_t* default_file_path,
|
||||||
cef_string_list_t accept_filters,
|
cef_string_list_t accept_filters,
|
||||||
|
cef_string_list_t accept_extensions,
|
||||||
|
cef_string_list_t accept_descriptions,
|
||||||
struct _cef_file_dialog_callback_t* callback);
|
struct _cef_file_dialog_callback_t* callback);
|
||||||
} cef_dialog_handler_t;
|
} cef_dialog_handler_t;
|
||||||
|
|
||||||
|
|
|
@ -42,13 +42,13 @@
|
||||||
// way that may cause binary incompatibility with other builds. The universal
|
// way that may cause binary incompatibility with other builds. The universal
|
||||||
// hash value will change if any platform is affected whereas the platform hash
|
// hash value will change if any platform is affected whereas the platform hash
|
||||||
// values will change only if that particular platform is affected.
|
// values will change only if that particular platform is affected.
|
||||||
#define CEF_API_HASH_UNIVERSAL "100bd8428968266a67128c02f63e2fd5aaa90d28"
|
#define CEF_API_HASH_UNIVERSAL "3ecad1c27a9f10720824f4c4be478dab28f1258d"
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
#define CEF_API_HASH_PLATFORM "d5cba94f734fb1966f098fad5cc5f342a5d14afe"
|
#define CEF_API_HASH_PLATFORM "551fbcb8ec2687c554be82cc10f471201c88727b"
|
||||||
#elif defined(OS_MAC)
|
#elif defined(OS_MAC)
|
||||||
#define CEF_API_HASH_PLATFORM "f5554ef4857432df5d22a14a9df7f3b1248c3265"
|
#define CEF_API_HASH_PLATFORM "4ca22671083a799f3c8c09905804882988ebd97b"
|
||||||
#elif defined(OS_LINUX)
|
#elif defined(OS_LINUX)
|
||||||
#define CEF_API_HASH_PLATFORM "5c283fac1f398ae4a5a5c2f16a4984267034950f"
|
#define CEF_API_HASH_PLATFORM "fb827bbeab0a16044f5cee11912d7dfc2ad44999"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -77,21 +77,30 @@ class CefDialogHandler : public virtual CefBaseRefCounted {
|
||||||
/// empty to show the default title ("Open" or "Save" depending on the mode).
|
/// empty to show the default title ("Open" or "Save" depending on the mode).
|
||||||
/// |default_file_path| is the path with optional directory and/or file name
|
/// |default_file_path| is the path with optional directory and/or file name
|
||||||
/// component that should be initially selected in the dialog.
|
/// component that should be initially selected in the dialog.
|
||||||
/// |accept_filters| are used to restrict the selectable file types and may
|
/// |accept_filters| are used to restrict the selectable file types and may be
|
||||||
/// any combination of (a) valid lower-cased MIME types (e.g. "text/*" or
|
/// any combination of valid lower-cased MIME types (e.g. "text/*" or
|
||||||
/// "image/*"), (b) individual file extensions (e.g. ".txt" or ".png"), or (c)
|
/// "image/*") and individual file extensions (e.g. ".txt" or ".png").
|
||||||
/// combined description and file extension delimited using "|" and ";" (e.g.
|
/// |accept_extensions| provides the semicolon-delimited expansion of MIME
|
||||||
/// "Image Types|.png;.gif;.jpg"). To display a custom dialog return true and
|
/// types to file extensions (if known, or empty string otherwise).
|
||||||
/// execute |callback| either inline or at a later time. To display the
|
/// |accept_descriptions| provides the descriptions for MIME types (if known,
|
||||||
/// default dialog return false.
|
/// or empty string otherwise). For example, the "image/*" mime type might
|
||||||
|
/// have extensions ".png;.jpg;.bmp;..." and description "Image Files".
|
||||||
|
/// |accept_filters|, |accept_extensions| and |accept_descriptions| will all
|
||||||
|
/// be the same size. To display a custom dialog return true and execute
|
||||||
|
/// |callback| either inline or at a later time. To display the default dialog
|
||||||
|
/// return false. If this method returns false it may be called an additional
|
||||||
|
/// time for the same dialog (both before and after MIME type expansion).
|
||||||
///
|
///
|
||||||
/*--cef(optional_param=title,optional_param=default_file_path,
|
/*--cef(optional_param=title,optional_param=default_file_path,
|
||||||
optional_param=accept_filters)--*/
|
optional_param=accept_filters,optional_param=accept_extensions,
|
||||||
|
optional_param=accept_descriptions)--*/
|
||||||
virtual bool OnFileDialog(CefRefPtr<CefBrowser> browser,
|
virtual bool OnFileDialog(CefRefPtr<CefBrowser> browser,
|
||||||
FileDialogMode mode,
|
FileDialogMode mode,
|
||||||
const CefString& title,
|
const CefString& title,
|
||||||
const CefString& default_file_path,
|
const CefString& default_file_path,
|
||||||
const std::vector<CefString>& accept_filters,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
CefRefPtr<CefFileDialogCallback> callback) {
|
CefRefPtr<CefFileDialogCallback> callback) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,16 +141,19 @@ FileChooserParams SelectFileToFileChooserParams(
|
||||||
params.title = title;
|
params.title = title;
|
||||||
params.default_file_name = default_path;
|
params.default_file_name = default_path;
|
||||||
|
|
||||||
// Note that this translation will lose any mime-type based filters that
|
|
||||||
// may have existed in the original FileChooserParams::accept_types if this
|
|
||||||
// dialog was created via FileSelectHelper::RunFileChooser.
|
|
||||||
if (file_types) {
|
if (file_types) {
|
||||||
// A list of allowed extensions. For example, it might be
|
// |file_types| comes from FileSelectHelper::GetFileTypesFromAcceptType.
|
||||||
|
// |extensions| is a list of allowed extensions. For example, it might be
|
||||||
// { { "htm", "html" }, { "txt" } }
|
// { { "htm", "html" }, { "txt" } }
|
||||||
for (auto& vec : file_types->extensions) {
|
for (size_t i = 0; i < file_types->extensions.size(); ++i) {
|
||||||
for (auto& ext : vec) {
|
if (!file_types->extension_mimetypes[i].empty()) {
|
||||||
params.accept_types.push_back(
|
// Use the original mime type.
|
||||||
FilePathTypeToString16(FILE_PATH_LITERAL(".") + ext));
|
params.accept_types.push_back(file_types->extension_mimetypes[i]);
|
||||||
|
} else if (file_types->extensions[i].size() == 1) {
|
||||||
|
// Use the single file extension. We ignore the "Custom Files" filter
|
||||||
|
// which is the only instance of multiple file extensions.
|
||||||
|
params.accept_types.push_back(FilePathTypeToString16(
|
||||||
|
FILE_PATH_LITERAL(".") + file_types->extensions[i][0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,8 +331,10 @@ void CefFileDialogManager::RunFileChooser(
|
||||||
// handled here there will be another call to the delegate from RunSelectFile.
|
// handled here there will be another call to the delegate from RunSelectFile.
|
||||||
// It might be better to execute the delegate only the single time here, but
|
// It might be better to execute the delegate only the single time here, but
|
||||||
// we don't currently have sufficient state in RunSelectFile to know that the
|
// we don't currently have sufficient state in RunSelectFile to know that the
|
||||||
// delegate has already been executed.
|
// delegate has already been executed. Also, we haven't retrieved file
|
||||||
callback = MaybeRunDelegate(params, std::move(callback));
|
// extension data at this point.
|
||||||
|
callback = MaybeRunDelegate(params, Extensions(), Descriptions(),
|
||||||
|
std::move(callback));
|
||||||
if (callback.is_null()) {
|
if (callback.is_null()) {
|
||||||
// The delegate kept the callback.
|
// The delegate kept the callback.
|
||||||
return;
|
return;
|
||||||
|
@ -377,14 +382,15 @@ void CefFileDialogManager::RunSelectFile(
|
||||||
|
|
||||||
active_listeners_.insert(listener);
|
active_listeners_.insert(listener);
|
||||||
|
|
||||||
// This will not be an exact representation of the original params.
|
|
||||||
auto chooser_params =
|
auto chooser_params =
|
||||||
SelectFileToFileChooserParams(type, title, default_path, file_types);
|
SelectFileToFileChooserParams(type, title, default_path, file_types);
|
||||||
auto callback =
|
auto callback =
|
||||||
base::BindOnce(&CefFileDialogManager::SelectFileDoneByDelegateCallback,
|
base::BindOnce(&CefFileDialogManager::SelectFileDoneByDelegateCallback,
|
||||||
weak_ptr_factory_.GetWeakPtr(), base::Unretained(listener),
|
weak_ptr_factory_.GetWeakPtr(), base::Unretained(listener),
|
||||||
base::Unretained(params));
|
base::Unretained(params));
|
||||||
callback = MaybeRunDelegate(chooser_params, std::move(callback));
|
callback = MaybeRunDelegate(chooser_params, file_types->extensions,
|
||||||
|
file_types->extension_description_overrides,
|
||||||
|
std::move(callback));
|
||||||
if (callback.is_null()) {
|
if (callback.is_null()) {
|
||||||
// The delegate kept the callback.
|
// The delegate kept the callback.
|
||||||
return;
|
return;
|
||||||
|
@ -456,7 +462,15 @@ void CefFileDialogManager::SelectFileListenerDestroyed(
|
||||||
CefFileDialogManager::RunFileChooserCallback
|
CefFileDialogManager::RunFileChooserCallback
|
||||||
CefFileDialogManager::MaybeRunDelegate(
|
CefFileDialogManager::MaybeRunDelegate(
|
||||||
const blink::mojom::FileChooserParams& params,
|
const blink::mojom::FileChooserParams& params,
|
||||||
|
const Extensions& extensions,
|
||||||
|
const Descriptions& descriptions,
|
||||||
RunFileChooserCallback callback) {
|
RunFileChooserCallback callback) {
|
||||||
|
// |extensions| and |descriptions| may be empty, or may contain 1 additional
|
||||||
|
// entry for the "Custom Files" filter.
|
||||||
|
DCHECK(extensions.empty() || extensions.size() >= params.accept_types.size());
|
||||||
|
DCHECK(descriptions.empty() ||
|
||||||
|
descriptions.size() >= params.accept_types.size());
|
||||||
|
|
||||||
if (auto client = browser_->client()) {
|
if (auto client = browser_->client()) {
|
||||||
if (auto handler = browser_->client()->GetDialogHandler()) {
|
if (auto handler = browser_->client()->GetDialogHandler()) {
|
||||||
int mode = FILE_DIALOG_OPEN;
|
int mode = FILE_DIALOG_OPEN;
|
||||||
|
@ -478,12 +492,37 @@ CefFileDialogManager::MaybeRunDelegate(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::u16string>::const_iterator it;
|
|
||||||
|
|
||||||
std::vector<CefString> accept_filters;
|
std::vector<CefString> accept_filters;
|
||||||
it = params.accept_types.begin();
|
for (auto& accept_type : params.accept_types) {
|
||||||
for (; it != params.accept_types.end(); ++it) {
|
accept_filters.push_back(accept_type);
|
||||||
accept_filters.push_back(*it);
|
}
|
||||||
|
|
||||||
|
std::vector<CefString> accept_extensions;
|
||||||
|
std::vector<CefString> accept_descriptions;
|
||||||
|
if (extensions.empty()) {
|
||||||
|
// We don't know the expansion of mime type values at this time,
|
||||||
|
// so only include the single file extensions.
|
||||||
|
for (auto& accept_type : params.accept_types) {
|
||||||
|
accept_extensions.push_back(
|
||||||
|
accept_type.ends_with(u"/*") ? std::u16string() : accept_type);
|
||||||
|
}
|
||||||
|
// Empty descriptions.
|
||||||
|
accept_descriptions.resize(params.accept_types.size());
|
||||||
|
} else {
|
||||||
|
// There may be 1 additional entry in |extensions| and |descriptions|
|
||||||
|
// that we want to ignore (for the "Custom Files" filter).
|
||||||
|
for (size_t i = 0; i < params.accept_types.size(); ++i) {
|
||||||
|
const auto& extension_list = extensions[i];
|
||||||
|
std::u16string ext_str;
|
||||||
|
for (auto& ext : extension_list) {
|
||||||
|
if (!ext_str.empty()) {
|
||||||
|
ext_str += u";";
|
||||||
|
}
|
||||||
|
ext_str += FilePathTypeToString16(FILE_PATH_LITERAL(".") + ext);
|
||||||
|
}
|
||||||
|
accept_extensions.push_back(ext_str);
|
||||||
|
accept_descriptions.push_back(descriptions[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CefRefPtr<CefFileDialogCallbackImpl> callbackImpl(
|
CefRefPtr<CefFileDialogCallbackImpl> callbackImpl(
|
||||||
|
@ -491,10 +530,13 @@ CefFileDialogManager::MaybeRunDelegate(
|
||||||
const bool handled = handler->OnFileDialog(
|
const bool handled = handler->OnFileDialog(
|
||||||
browser_.get(), static_cast<cef_file_dialog_mode_t>(mode),
|
browser_.get(), static_cast<cef_file_dialog_mode_t>(mode),
|
||||||
params.title, params.default_file_name.value(), accept_filters,
|
params.title, params.default_file_name.value(), accept_filters,
|
||||||
callbackImpl.get());
|
accept_extensions, accept_descriptions, callbackImpl.get());
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
// May return nullptr if the client has already executed the callback.
|
// May return nullptr if the client has already executed the callback.
|
||||||
callback = callbackImpl->Disconnect();
|
callback = callbackImpl->Disconnect();
|
||||||
|
LOG_IF(ERROR, callback.is_null())
|
||||||
|
<< "Should return true from OnFileDialog when executing the "
|
||||||
|
"callback";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,12 @@ class CefFileDialogManager {
|
||||||
void SelectFileListenerDestroyed(ui::SelectFileDialog::Listener* listener);
|
void SelectFileListenerDestroyed(ui::SelectFileDialog::Listener* listener);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using Extensions = std::vector<std::vector<base::FilePath::StringType>>;
|
||||||
|
using Descriptions = std::vector<std::u16string>;
|
||||||
[[nodiscard]] RunFileChooserCallback MaybeRunDelegate(
|
[[nodiscard]] RunFileChooserCallback MaybeRunDelegate(
|
||||||
const blink::mojom::FileChooserParams& params,
|
const blink::mojom::FileChooserParams& params,
|
||||||
|
const Extensions& extensions,
|
||||||
|
const Descriptions& descriptions,
|
||||||
RunFileChooserCallback callback);
|
RunFileChooserCallback callback);
|
||||||
|
|
||||||
void SelectFileDoneByDelegateCallback(
|
void SelectFileDoneByDelegateCallback(
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
// implementations. See the translator.README.txt file in the tools directory
|
// implementations. See the translator.README.txt file in the tools directory
|
||||||
// for more information.
|
// for more information.
|
||||||
//
|
//
|
||||||
// $hash=1bb6115ab6501d8d006585cede00970e59af9868$
|
// $hash=3c95f12406eee58308a2c019e8081e0710769e0d$
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "libcef_dll/cpptoc/dialog_handler_cpptoc.h"
|
#include "libcef_dll/cpptoc/dialog_handler_cpptoc.h"
|
||||||
|
@ -30,6 +30,8 @@ dialog_handler_on_file_dialog(struct _cef_dialog_handler_t* self,
|
||||||
const cef_string_t* title,
|
const cef_string_t* title,
|
||||||
const cef_string_t* default_file_path,
|
const cef_string_t* default_file_path,
|
||||||
cef_string_list_t accept_filters,
|
cef_string_list_t accept_filters,
|
||||||
|
cef_string_list_t accept_extensions,
|
||||||
|
cef_string_list_t accept_descriptions,
|
||||||
cef_file_dialog_callback_t* callback) {
|
cef_file_dialog_callback_t* callback) {
|
||||||
shutdown_checker::AssertNotShutdown();
|
shutdown_checker::AssertNotShutdown();
|
||||||
|
|
||||||
|
@ -49,17 +51,24 @@ dialog_handler_on_file_dialog(struct _cef_dialog_handler_t* self,
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Unverified params: title, default_file_path, accept_filters
|
// Unverified params: title, default_file_path, accept_filters,
|
||||||
|
// accept_extensions, accept_descriptions
|
||||||
|
|
||||||
// Translate param: accept_filters; type: string_vec_byref_const
|
// Translate param: accept_filters; type: string_vec_byref_const
|
||||||
std::vector<CefString> accept_filtersList;
|
std::vector<CefString> accept_filtersList;
|
||||||
transfer_string_list_contents(accept_filters, accept_filtersList);
|
transfer_string_list_contents(accept_filters, accept_filtersList);
|
||||||
|
// Translate param: accept_extensions; type: string_vec_byref_const
|
||||||
|
std::vector<CefString> accept_extensionsList;
|
||||||
|
transfer_string_list_contents(accept_extensions, accept_extensionsList);
|
||||||
|
// Translate param: accept_descriptions; type: string_vec_byref_const
|
||||||
|
std::vector<CefString> accept_descriptionsList;
|
||||||
|
transfer_string_list_contents(accept_descriptions, accept_descriptionsList);
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
bool _retval = CefDialogHandlerCppToC::Get(self)->OnFileDialog(
|
bool _retval = CefDialogHandlerCppToC::Get(self)->OnFileDialog(
|
||||||
CefBrowserCToCpp::Wrap(browser), mode, CefString(title),
|
CefBrowserCToCpp::Wrap(browser), mode, CefString(title),
|
||||||
CefString(default_file_path), accept_filtersList,
|
CefString(default_file_path), accept_filtersList, accept_extensionsList,
|
||||||
CefFileDialogCallbackCToCpp::Wrap(callback));
|
accept_descriptionsList, CefFileDialogCallbackCToCpp::Wrap(callback));
|
||||||
|
|
||||||
// Return type: bool
|
// Return type: bool
|
||||||
return _retval;
|
return _retval;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
// implementations. See the translator.README.txt file in the tools directory
|
// implementations. See the translator.README.txt file in the tools directory
|
||||||
// for more information.
|
// for more information.
|
||||||
//
|
//
|
||||||
// $hash=40bd7cc8a8fd361aceda9bbd426f912ac140ac3d$
|
// $hash=bd2c8c9be10525d69d072dacfb3cfd215486e077$
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "libcef_dll/ctocpp/dialog_handler_ctocpp.h"
|
#include "libcef_dll/ctocpp/dialog_handler_ctocpp.h"
|
||||||
|
@ -28,6 +28,8 @@ bool CefDialogHandlerCToCpp::OnFileDialog(
|
||||||
const CefString& title,
|
const CefString& title,
|
||||||
const CefString& default_file_path,
|
const CefString& default_file_path,
|
||||||
const std::vector<CefString>& accept_filters,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
CefRefPtr<CefFileDialogCallback> callback) {
|
CefRefPtr<CefFileDialogCallback> callback) {
|
||||||
shutdown_checker::AssertNotShutdown();
|
shutdown_checker::AssertNotShutdown();
|
||||||
|
|
||||||
|
@ -48,7 +50,8 @@ bool CefDialogHandlerCToCpp::OnFileDialog(
|
||||||
if (!callback.get()) {
|
if (!callback.get()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Unverified params: title, default_file_path, accept_filters
|
// Unverified params: title, default_file_path, accept_filters,
|
||||||
|
// accept_extensions, accept_descriptions
|
||||||
|
|
||||||
// Translate param: accept_filters; type: string_vec_byref_const
|
// Translate param: accept_filters; type: string_vec_byref_const
|
||||||
cef_string_list_t accept_filtersList = cef_string_list_alloc();
|
cef_string_list_t accept_filtersList = cef_string_list_alloc();
|
||||||
|
@ -56,17 +59,37 @@ bool CefDialogHandlerCToCpp::OnFileDialog(
|
||||||
if (accept_filtersList) {
|
if (accept_filtersList) {
|
||||||
transfer_string_list_contents(accept_filters, accept_filtersList);
|
transfer_string_list_contents(accept_filters, accept_filtersList);
|
||||||
}
|
}
|
||||||
|
// Translate param: accept_extensions; type: string_vec_byref_const
|
||||||
|
cef_string_list_t accept_extensionsList = cef_string_list_alloc();
|
||||||
|
DCHECK(accept_extensionsList);
|
||||||
|
if (accept_extensionsList) {
|
||||||
|
transfer_string_list_contents(accept_extensions, accept_extensionsList);
|
||||||
|
}
|
||||||
|
// Translate param: accept_descriptions; type: string_vec_byref_const
|
||||||
|
cef_string_list_t accept_descriptionsList = cef_string_list_alloc();
|
||||||
|
DCHECK(accept_descriptionsList);
|
||||||
|
if (accept_descriptionsList) {
|
||||||
|
transfer_string_list_contents(accept_descriptions, accept_descriptionsList);
|
||||||
|
}
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
int _retval = _struct->on_file_dialog(
|
int _retval = _struct->on_file_dialog(
|
||||||
_struct, CefBrowserCppToC::Wrap(browser), mode, title.GetStruct(),
|
_struct, CefBrowserCppToC::Wrap(browser), mode, title.GetStruct(),
|
||||||
default_file_path.GetStruct(), accept_filtersList,
|
default_file_path.GetStruct(), accept_filtersList, accept_extensionsList,
|
||||||
CefFileDialogCallbackCppToC::Wrap(callback));
|
accept_descriptionsList, CefFileDialogCallbackCppToC::Wrap(callback));
|
||||||
|
|
||||||
// Restore param:accept_filters; type: string_vec_byref_const
|
// Restore param:accept_filters; type: string_vec_byref_const
|
||||||
if (accept_filtersList) {
|
if (accept_filtersList) {
|
||||||
cef_string_list_free(accept_filtersList);
|
cef_string_list_free(accept_filtersList);
|
||||||
}
|
}
|
||||||
|
// Restore param:accept_extensions; type: string_vec_byref_const
|
||||||
|
if (accept_extensionsList) {
|
||||||
|
cef_string_list_free(accept_extensionsList);
|
||||||
|
}
|
||||||
|
// Restore param:accept_descriptions; type: string_vec_byref_const
|
||||||
|
if (accept_descriptionsList) {
|
||||||
|
cef_string_list_free(accept_descriptionsList);
|
||||||
|
}
|
||||||
|
|
||||||
// Return type: bool
|
// Return type: bool
|
||||||
return _retval ? true : false;
|
return _retval ? true : false;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
// implementations. See the translator.README.txt file in the tools directory
|
// implementations. See the translator.README.txt file in the tools directory
|
||||||
// for more information.
|
// for more information.
|
||||||
//
|
//
|
||||||
// $hash=b9efac38dfb7834bd87b6c0dfd29e0fc3179b6ee$
|
// $hash=48e8da3e0a0ce27bf61f24f601d0ca179d232b90$
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef CEF_LIBCEF_DLL_CTOCPP_DIALOG_HANDLER_CTOCPP_H_
|
#ifndef CEF_LIBCEF_DLL_CTOCPP_DIALOG_HANDLER_CTOCPP_H_
|
||||||
|
@ -42,6 +42,8 @@ class CefDialogHandlerCToCpp
|
||||||
const CefString& title,
|
const CefString& title,
|
||||||
const CefString& default_file_path,
|
const CefString& default_file_path,
|
||||||
const std::vector<CefString>& accept_filters,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
CefRefPtr<CefFileDialogCallback> callback) override;
|
CefRefPtr<CefFileDialogCallback> callback) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
diff --git chrome/browser/file_select_helper.cc chrome/browser/file_select_helper.cc
|
diff --git chrome/browser/file_select_helper.cc chrome/browser/file_select_helper.cc
|
||||||
index 447a91b9ac380..cf5ad1b907977 100644
|
index 447a91b9ac380..0452f0a8b57cf 100644
|
||||||
--- chrome/browser/file_select_helper.cc
|
--- chrome/browser/file_select_helper.cc
|
||||||
+++ chrome/browser/file_select_helper.cc
|
+++ chrome/browser/file_select_helper.cc
|
||||||
@@ -20,6 +20,7 @@
|
@@ -20,6 +20,7 @@
|
||||||
|
@ -43,30 +43,23 @@ index 447a91b9ac380..cf5ad1b907977 100644
|
||||||
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
|
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
|
||||||
enterprise_connectors::ContentAnalysisDelegate::Data data;
|
enterprise_connectors::ContentAnalysisDelegate::Data data;
|
||||||
if (enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
|
if (enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
|
||||||
@@ -459,7 +479,8 @@ void FileSelectHelper::DontAbortOnMissingWebContentsForTesting() {
|
@@ -472,31 +492,51 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
||||||
|
|
||||||
std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
|
|
||||||
FileSelectHelper::GetFileTypesFromAcceptType(
|
|
||||||
- const std::vector<std::u16string>& accept_types) {
|
|
||||||
+ const std::vector<std::u16string>& accept_types,
|
|
||||||
+ bool run_from_cef) {
|
|
||||||
auto base_file_type = std::make_unique<ui::SelectFileDialog::FileTypeInfo>();
|
|
||||||
if (accept_types.empty())
|
|
||||||
return base_file_type;
|
|
||||||
@@ -472,17 +493,24 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
|
||||||
std::vector<base::FilePath::StringType>* extensions =
|
std::vector<base::FilePath::StringType>* extensions =
|
||||||
&file_type->extensions.back();
|
&file_type->extensions.back();
|
||||||
|
|
||||||
+ // Create individual filters for each accept type.
|
+ // Create individual filters for each accept type.
|
||||||
+ std::vector<std::vector<base::FilePath::StringType>> all_extensions;
|
+ std::vector<std::vector<base::FilePath::StringType>> all_extensions;
|
||||||
+ std::vector<std::u16string> all_overrides;
|
+ std::vector<std::u16string> all_overrides;
|
||||||
|
+ std::vector<std::u16string> all_mimetypes;
|
||||||
+
|
+
|
||||||
// Find the corresponding extensions.
|
// Find the corresponding extensions.
|
||||||
int valid_type_count = 0;
|
int valid_type_count = 0;
|
||||||
int description_id = 0;
|
int description_id = 0;
|
||||||
|
+ std::string ascii_type;
|
||||||
for (const auto& accept_type : accept_types) {
|
for (const auto& accept_type : accept_types) {
|
||||||
+ std::vector<base::FilePath::StringType> current_extensions;
|
+ std::vector<base::FilePath::StringType> current_extensions;
|
||||||
+ description_id = 0;
|
+ description_id = 0;
|
||||||
|
+ ascii_type.clear();
|
||||||
+
|
+
|
||||||
size_t old_extension_size = extensions->size();
|
size_t old_extension_size = extensions->size();
|
||||||
if (accept_type[0] == '.') {
|
if (accept_type[0] == '.') {
|
||||||
|
@ -79,7 +72,11 @@ index 447a91b9ac380..cf5ad1b907977 100644
|
||||||
} else {
|
} else {
|
||||||
if (!base::IsStringASCII(accept_type))
|
if (!base::IsStringASCII(accept_type))
|
||||||
continue;
|
continue;
|
||||||
@@ -493,10 +521,18 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
- std::string ascii_type = base::UTF16ToASCII(accept_type);
|
||||||
|
+ ascii_type = base::UTF16ToASCII(accept_type);
|
||||||
|
if (ascii_type == "image/*")
|
||||||
|
description_id = IDS_IMAGE_FILES;
|
||||||
|
else if (ascii_type == "audio/*")
|
||||||
description_id = IDS_AUDIO_FILES;
|
description_id = IDS_AUDIO_FILES;
|
||||||
else if (ascii_type == "video/*")
|
else if (ascii_type == "video/*")
|
||||||
description_id = IDS_VIDEO_FILES;
|
description_id = IDS_VIDEO_FILES;
|
||||||
|
@ -93,6 +90,8 @@ index 447a91b9ac380..cf5ad1b907977 100644
|
||||||
+ all_overrides.push_back(description_id != 0 ?
|
+ all_overrides.push_back(description_id != 0 ?
|
||||||
+ l10n_util::GetStringUTF16(description_id) :
|
+ l10n_util::GetStringUTF16(description_id) :
|
||||||
+ std::u16string());
|
+ std::u16string());
|
||||||
|
+ all_mimetypes.push_back(ascii_type.empty() ?
|
||||||
|
+ std::u16string() : accept_type);
|
||||||
+
|
+
|
||||||
+ extensions->insert(extensions->end(), current_extensions.begin(),
|
+ extensions->insert(extensions->end(), current_extensions.begin(),
|
||||||
+ current_extensions.end());
|
+ current_extensions.end());
|
||||||
|
@ -100,23 +99,40 @@ index 447a91b9ac380..cf5ad1b907977 100644
|
||||||
if (extensions->size() > old_extension_size)
|
if (extensions->size() > old_extension_size)
|
||||||
valid_type_count++;
|
valid_type_count++;
|
||||||
}
|
}
|
||||||
@@ -521,6 +557,15 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
@@ -513,12 +553,28 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
||||||
l10n_util::GetStringUTF16(description_id));
|
// dialog uses the first extension in the list to form the description,
|
||||||
}
|
// like "EHTML Files". This is not what we want.
|
||||||
|
if (valid_type_count > 1 ||
|
||||||
|
- (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
|
||||||
|
+ (valid_type_count == 1 && description_id == 0 && extensions->size() > 1)) {
|
||||||
|
description_id = IDS_CUSTOM_FILES;
|
||||||
|
+ ascii_type.clear();
|
||||||
|
+ }
|
||||||
|
|
||||||
+ if (run_from_cef && all_extensions.size() > 1) {
|
- if (description_id) {
|
||||||
|
- file_type->extension_description_overrides.push_back(
|
||||||
|
- l10n_util::GetStringUTF16(description_id));
|
||||||
|
+ file_type->extension_description_overrides.push_back(
|
||||||
|
+ description_id != 0 ?
|
||||||
|
+ l10n_util::GetStringUTF16(description_id) :
|
||||||
|
+ std::u16string());
|
||||||
|
+ file_type->extension_mimetypes.push_back(
|
||||||
|
+ ascii_type.empty() ? std::u16string() : base::ASCIIToUTF16(ascii_type));
|
||||||
|
+
|
||||||
|
+ if (all_extensions.size() > 1) {
|
||||||
+ // Insert filters for the specific accept types at the beginning.
|
+ // Insert filters for the specific accept types at the beginning.
|
||||||
+ file_type->extensions.insert(file_type->extensions.begin(),
|
+ file_type->extensions.insert(file_type->extensions.begin(),
|
||||||
+ all_extensions.begin(), all_extensions.end());
|
+ all_extensions.begin(), all_extensions.end());
|
||||||
+ file_type->extension_description_overrides.insert(
|
+ file_type->extension_description_overrides.insert(
|
||||||
+ file_type->extension_description_overrides.begin(),
|
+ file_type->extension_description_overrides.begin(),
|
||||||
+ all_overrides.begin(), all_overrides.end());
|
+ all_overrides.begin(), all_overrides.end());
|
||||||
+ }
|
+ file_type->extension_mimetypes.insert(
|
||||||
+
|
+ file_type->extension_mimetypes.begin(),
|
||||||
return file_type;
|
+ all_mimetypes.begin(), all_mimetypes.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +573,8 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
return file_type;
|
||||||
|
@@ -528,7 +584,8 @@ FileSelectHelper::GetFileTypesFromAcceptType(
|
||||||
void FileSelectHelper::RunFileChooser(
|
void FileSelectHelper::RunFileChooser(
|
||||||
content::RenderFrameHost* render_frame_host,
|
content::RenderFrameHost* render_frame_host,
|
||||||
scoped_refptr<content::FileSelectListener> listener,
|
scoped_refptr<content::FileSelectListener> listener,
|
||||||
|
@ -126,7 +142,7 @@ index 447a91b9ac380..cf5ad1b907977 100644
|
||||||
Profile* profile = Profile::FromBrowserContext(
|
Profile* profile = Profile::FromBrowserContext(
|
||||||
render_frame_host->GetProcess()->GetBrowserContext());
|
render_frame_host->GetProcess()->GetBrowserContext());
|
||||||
|
|
||||||
@@ -547,6 +593,7 @@ void FileSelectHelper::RunFileChooser(
|
@@ -547,6 +604,7 @@ void FileSelectHelper::RunFileChooser(
|
||||||
// message.
|
// message.
|
||||||
scoped_refptr<FileSelectHelper> file_select_helper(
|
scoped_refptr<FileSelectHelper> file_select_helper(
|
||||||
new FileSelectHelper(profile));
|
new FileSelectHelper(profile));
|
||||||
|
@ -134,18 +150,8 @@ index 447a91b9ac380..cf5ad1b907977 100644
|
||||||
file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
|
file_select_helper->RunFileChooser(render_frame_host, std::move(listener),
|
||||||
params.Clone());
|
params.Clone());
|
||||||
}
|
}
|
||||||
@@ -598,7 +645,8 @@ void FileSelectHelper::RunFileChooser(
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileSelectHelper::GetFileTypesInThreadPool(FileChooserParamsPtr params) {
|
|
||||||
- select_file_types_ = GetFileTypesFromAcceptType(params->accept_types);
|
|
||||||
+ select_file_types_ = GetFileTypesFromAcceptType(params->accept_types,
|
|
||||||
+ run_from_cef_);
|
|
||||||
select_file_types_->allowed_paths =
|
|
||||||
params->need_local_path ? ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH
|
|
||||||
: ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
|
|
||||||
diff --git chrome/browser/file_select_helper.h chrome/browser/file_select_helper.h
|
diff --git chrome/browser/file_select_helper.h chrome/browser/file_select_helper.h
|
||||||
index 7194323c36956..903cdf0c294fc 100644
|
index 7194323c36956..fd4a5361798f0 100644
|
||||||
--- chrome/browser/file_select_helper.h
|
--- chrome/browser/file_select_helper.h
|
||||||
+++ chrome/browser/file_select_helper.h
|
+++ chrome/browser/file_select_helper.h
|
||||||
@@ -62,7 +62,8 @@ class FileSelectHelper : public base::RefCountedThreadSafe<
|
@@ -62,7 +62,8 @@ class FileSelectHelper : public base::RefCountedThreadSafe<
|
||||||
|
@ -158,17 +164,7 @@ index 7194323c36956..903cdf0c294fc 100644
|
||||||
|
|
||||||
// Enumerates all the files in directory.
|
// Enumerates all the files in directory.
|
||||||
static void EnumerateDirectory(
|
static void EnumerateDirectory(
|
||||||
@@ -264,7 +265,8 @@ class FileSelectHelper : public base::RefCountedThreadSafe<
|
@@ -325,6 +326,9 @@ class FileSelectHelper : public base::RefCountedThreadSafe<
|
||||||
// |accept_types| contains only valid lowercased MIME types or file extensions
|
|
||||||
// beginning with a period (.).
|
|
||||||
static std::unique_ptr<ui::SelectFileDialog::FileTypeInfo>
|
|
||||||
- GetFileTypesFromAcceptType(const std::vector<std::u16string>& accept_types);
|
|
||||||
+ GetFileTypesFromAcceptType(const std::vector<std::u16string>& accept_types,
|
|
||||||
+ bool run_from_cef);
|
|
||||||
|
|
||||||
// Check the accept type is valid. It is expected to be all lower case with
|
|
||||||
// no whitespace.
|
|
||||||
@@ -325,6 +327,9 @@ class FileSelectHelper : public base::RefCountedThreadSafe<
|
|
||||||
// Set to false in unit tests since there is no WebContents.
|
// Set to false in unit tests since there is no WebContents.
|
||||||
bool abort_on_missing_web_contents_in_tests_ = true;
|
bool abort_on_missing_web_contents_in_tests_ = true;
|
||||||
|
|
||||||
|
@ -261,7 +257,7 @@ index 68dd62159b686..e94831cd44a2d 100644
|
||||||
return CreateSelectFileDialog(listener, std::move(policy));
|
return CreateSelectFileDialog(listener, std::move(policy));
|
||||||
}
|
}
|
||||||
diff --git ui/shell_dialogs/select_file_dialog.h ui/shell_dialogs/select_file_dialog.h
|
diff --git ui/shell_dialogs/select_file_dialog.h ui/shell_dialogs/select_file_dialog.h
|
||||||
index 9b12fae59c3cc..dfb534a6f06e6 100644
|
index 9b12fae59c3cc..a80873f421c8d 100644
|
||||||
--- ui/shell_dialogs/select_file_dialog.h
|
--- ui/shell_dialogs/select_file_dialog.h
|
||||||
+++ ui/shell_dialogs/select_file_dialog.h
|
+++ ui/shell_dialogs/select_file_dialog.h
|
||||||
@@ -102,7 +102,8 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
@@ -102,7 +102,8 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||||
|
@ -274,7 +270,18 @@ index 9b12fae59c3cc..dfb534a6f06e6 100644
|
||||||
|
|
||||||
SelectFileDialog(const SelectFileDialog&) = delete;
|
SelectFileDialog(const SelectFileDialog&) = delete;
|
||||||
SelectFileDialog& operator=(const SelectFileDialog&) = delete;
|
SelectFileDialog& operator=(const SelectFileDialog&) = delete;
|
||||||
@@ -199,6 +200,19 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
@@ -126,6 +127,10 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||||
|
// be used.
|
||||||
|
std::vector<std::u16string> extension_description_overrides;
|
||||||
|
|
||||||
|
+ // Original mime types for the specified extensions. Entries correspond to
|
||||||
|
+ // |extensions|; if left blank then there was no mime type.
|
||||||
|
+ std::vector<std::u16string> extension_mimetypes;
|
||||||
|
+
|
||||||
|
// Specifies whether there will be a filter added for all files (i.e. *.*).
|
||||||
|
bool include_all_files = false;
|
||||||
|
|
||||||
|
@@ -199,6 +204,19 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||||
const GURL* caller = nullptr);
|
const GURL* caller = nullptr);
|
||||||
bool HasMultipleFileTypeChoices();
|
bool HasMultipleFileTypeChoices();
|
||||||
|
|
||||||
|
@ -294,7 +301,7 @@ index 9b12fae59c3cc..dfb534a6f06e6 100644
|
||||||
protected:
|
protected:
|
||||||
friend class base::RefCountedThreadSafe<SelectFileDialog>;
|
friend class base::RefCountedThreadSafe<SelectFileDialog>;
|
||||||
|
|
||||||
@@ -224,6 +238,11 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
@@ -224,6 +242,11 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||||
// The listener to be notified of selection completion.
|
// The listener to be notified of selection completion.
|
||||||
raw_ptr<Listener> listener_;
|
raw_ptr<Listener> listener_;
|
||||||
|
|
||||||
|
@ -306,7 +313,7 @@ index 9b12fae59c3cc..dfb534a6f06e6 100644
|
||||||
private:
|
private:
|
||||||
// Tests if the file selection dialog can be displayed by
|
// Tests if the file selection dialog can be displayed by
|
||||||
// testing if the AllowFileSelectionDialogs-Policy is
|
// testing if the AllowFileSelectionDialogs-Policy is
|
||||||
@@ -236,8 +255,6 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
@@ -236,8 +259,6 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||||
|
|
||||||
// Returns true if the dialog has multiple file type choices.
|
// Returns true if the dialog has multiple file type choices.
|
||||||
virtual bool HasMultipleFileTypeChoicesImpl() = 0;
|
virtual bool HasMultipleFileTypeChoicesImpl() = 0;
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "include/base/cef_logging.h"
|
||||||
#include "include/cef_browser.h"
|
#include "include/cef_browser.h"
|
||||||
#include "include/cef_parser.h"
|
#include "include/cef_parser.h"
|
||||||
#include "include/wrapper/cef_helpers.h"
|
#include "include/wrapper/cef_helpers.h"
|
||||||
#include "tests/cefclient/browser/root_window.h"
|
#include "tests/cefclient/browser/root_window.h"
|
||||||
#include "tests/cefclient/browser/util_gtk.h"
|
#include "tests/cefclient/browser/util_gtk.h"
|
||||||
|
#include "tests/shared/common/string_util.h"
|
||||||
|
|
||||||
namespace client {
|
namespace client {
|
||||||
|
|
||||||
|
@ -49,11 +51,14 @@ std::string GetDescriptionFromMimeType(const std::string& mime_type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG(WARNING) << "Unrecognized mime type: " << mime_type;
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddFilters(GtkFileChooser* chooser,
|
void AddFilters(GtkFileChooser* chooser,
|
||||||
const std::vector<CefString>& accept_filters,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
bool include_all_files,
|
bool include_all_files,
|
||||||
std::vector<GtkFileFilter*>* filters) {
|
std::vector<GtkFileFilter*>* filters) {
|
||||||
bool has_filter = false;
|
bool has_filter = false;
|
||||||
|
@ -64,33 +69,13 @@ void AddFilters(GtkFileChooser* chooser,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> extensions;
|
// Extensions and descriptions may be provided.
|
||||||
std::string description;
|
std::vector<std::string> extensions =
|
||||||
|
AsciiStrSplit(accept_extensions[j], ';');
|
||||||
size_t sep_index = filter.find('|');
|
std::string description = accept_descriptions[j];
|
||||||
if (sep_index != std::string::npos) {
|
|
||||||
// Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3".
|
|
||||||
description = filter.substr(0, sep_index);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
if (extensions.empty()) {
|
||||||
|
// Try to convert mime type to one or more extensions.
|
||||||
std::vector<CefString> ext;
|
std::vector<CefString> ext;
|
||||||
CefGetExtensionsForMimeType(filter, ext);
|
CefGetExtensionsForMimeType(filter, ext);
|
||||||
for (size_t x = 0; x < ext.size(); ++x) {
|
for (size_t x = 0; x < ext.size(); ++x) {
|
||||||
|
@ -102,6 +87,10 @@ void AddFilters(GtkFileChooser* chooser,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (description.empty() && filter[0] != '.') {
|
||||||
|
description = GetDescriptionFromMimeType(filter);
|
||||||
|
}
|
||||||
|
|
||||||
GtkFileFilter* gtk_filter = gtk_file_filter_new();
|
GtkFileFilter* gtk_filter = gtk_file_filter_new();
|
||||||
|
|
||||||
std::string ext_str;
|
std::string ext_str;
|
||||||
|
@ -154,6 +143,24 @@ GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if |accept_filters| contains a MIME type value without matching
|
||||||
|
// extensions in |accept_extensions|.
|
||||||
|
bool MissingMimeTypeData(const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions) {
|
||||||
|
if (accept_filters.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < accept_filters.size(); ++i) {
|
||||||
|
const std::string& filter = accept_filters[i];
|
||||||
|
if (filter[0] != '.' && accept_extensions[i].empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(nullptr) {}
|
ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(nullptr) {}
|
||||||
|
@ -164,8 +171,17 @@ bool ClientDialogHandlerGtk::OnFileDialog(
|
||||||
const CefString& title,
|
const CefString& title,
|
||||||
const CefString& default_file_path,
|
const CefString& default_file_path,
|
||||||
const std::vector<CefString>& accept_filters,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
CefRefPtr<CefFileDialogCallback> callback) {
|
CefRefPtr<CefFileDialogCallback> callback) {
|
||||||
CEF_REQUIRE_UI_THREAD();
|
CEF_REQUIRE_UI_THREAD();
|
||||||
|
DCHECK(accept_filters.size() == accept_extensions.size() ==
|
||||||
|
accept_descriptions.size());
|
||||||
|
|
||||||
|
if (MissingMimeTypeData(accept_filters, accept_extensions)) {
|
||||||
|
// Wait for the 2nd call that provides MIME type data.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
OnFileDialogParams params;
|
OnFileDialogParams params;
|
||||||
params.browser = browser;
|
params.browser = browser;
|
||||||
|
@ -173,6 +189,8 @@ bool ClientDialogHandlerGtk::OnFileDialog(
|
||||||
params.title = title;
|
params.title = title;
|
||||||
params.default_file_path = default_file_path;
|
params.default_file_path = default_file_path;
|
||||||
params.accept_filters = accept_filters;
|
params.accept_filters = accept_filters;
|
||||||
|
params.accept_extensions = accept_extensions;
|
||||||
|
params.accept_descriptions = accept_descriptions;
|
||||||
params.callback = callback;
|
params.callback = callback;
|
||||||
|
|
||||||
GetWindowAndContinue(
|
GetWindowAndContinue(
|
||||||
|
@ -309,7 +327,9 @@ void ClientDialogHandlerGtk::OnFileDialogContinue(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GtkFileFilter*> filters;
|
std::vector<GtkFileFilter*> filters;
|
||||||
AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters, true, &filters);
|
AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters,
|
||||||
|
params.accept_extensions, params.accept_descriptions, true,
|
||||||
|
&filters);
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ class ClientDialogHandlerGtk : public CefDialogHandler,
|
||||||
const CefString& title,
|
const CefString& title,
|
||||||
const CefString& default_file_path,
|
const CefString& default_file_path,
|
||||||
const std::vector<CefString>& accept_filters,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
CefRefPtr<CefFileDialogCallback> callback) override;
|
CefRefPtr<CefFileDialogCallback> callback) override;
|
||||||
|
|
||||||
// CefJSDialogHandler methods.
|
// CefJSDialogHandler methods.
|
||||||
|
@ -48,6 +50,8 @@ class ClientDialogHandlerGtk : public CefDialogHandler,
|
||||||
CefString title;
|
CefString title;
|
||||||
CefString default_file_path;
|
CefString default_file_path;
|
||||||
std::vector<CefString> accept_filters;
|
std::vector<CefString> accept_filters;
|
||||||
|
std::vector<CefString> accept_extensions;
|
||||||
|
std::vector<CefString> accept_descriptions;
|
||||||
CefRefPtr<CefFileDialogCallback> callback;
|
CefRefPtr<CefFileDialogCallback> callback;
|
||||||
};
|
};
|
||||||
void OnFileDialogContinue(const OnFileDialogParams& params,
|
void OnFileDialogContinue(const OnFileDialogParams& params,
|
||||||
|
|
|
@ -140,16 +140,11 @@ class Handler : public CefMessageRouterBrowserSide::Handler {
|
||||||
if (accept_filters.empty() &&
|
if (accept_filters.empty() &&
|
||||||
dialog_state_->mode_ != FILE_DIALOG_OPEN_FOLDER) {
|
dialog_state_->mode_ != FILE_DIALOG_OPEN_FOLDER) {
|
||||||
// Build filters based on mime time.
|
// Build filters based on mime time.
|
||||||
accept_filters.push_back("text/*");
|
accept_filters.push_back("image/*");
|
||||||
|
|
||||||
// Build filters based on file extension.
|
// Build filters based on file extension.
|
||||||
accept_filters.push_back(".log");
|
accept_filters.push_back(".log");
|
||||||
accept_filters.push_back(".patch");
|
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;
|
dialog_state_->pending_ = true;
|
||||||
|
|
|
@ -19,15 +19,22 @@ class DialogTestHandler : public TestHandler {
|
||||||
: mode(dialog_mode),
|
: mode(dialog_mode),
|
||||||
title("Test Title"),
|
title("Test Title"),
|
||||||
default_file_name("Test File Name") {
|
default_file_name("Test File Name") {
|
||||||
accept_types.push_back("text/*");
|
accept_filters.push_back("image/*");
|
||||||
accept_types.push_back(".js");
|
// We're handling the dialog before MIME type expansion.
|
||||||
accept_types.push_back(".css");
|
accept_extensions.push_back(CefString());
|
||||||
|
accept_filters.push_back(".js");
|
||||||
|
accept_extensions.push_back(".js");
|
||||||
|
accept_filters.push_back(".css");
|
||||||
|
accept_extensions.push_back(".css");
|
||||||
}
|
}
|
||||||
|
|
||||||
FileDialogMode mode;
|
FileDialogMode mode;
|
||||||
CefString title;
|
CefString title;
|
||||||
CefString default_file_name;
|
CefString default_file_name;
|
||||||
std::vector<CefString> accept_types;
|
std::vector<CefString> accept_filters;
|
||||||
|
std::vector<CefString> accept_extensions;
|
||||||
|
|
||||||
|
bool skip_first_callback = false;
|
||||||
|
|
||||||
// True if the callback should execute asynchronously.
|
// True if the callback should execute asynchronously.
|
||||||
bool callback_async = false;
|
bool callback_async = false;
|
||||||
|
@ -76,9 +83,9 @@ class DialogTestHandler : public TestHandler {
|
||||||
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
|
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
|
||||||
CefRefPtr<CefFrame> frame,
|
CefRefPtr<CefFrame> frame,
|
||||||
int httpStatusCode) override {
|
int httpStatusCode) override {
|
||||||
browser->GetHost()->RunFileDialog(config_.mode, config_.title,
|
browser->GetHost()->RunFileDialog(
|
||||||
config_.default_file_name,
|
config_.mode, config_.title, config_.default_file_name,
|
||||||
config_.accept_types, new Callback(this));
|
config_.accept_filters, new Callback(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExecuteCallback(CefRefPtr<CefFileDialogCallback> callback) {
|
void ExecuteCallback(CefRefPtr<CefFileDialogCallback> callback) {
|
||||||
|
@ -94,18 +101,58 @@ class DialogTestHandler : public TestHandler {
|
||||||
FileDialogMode mode,
|
FileDialogMode mode,
|
||||||
const CefString& title,
|
const CefString& title,
|
||||||
const CefString& default_file_name,
|
const CefString& default_file_name,
|
||||||
const std::vector<CefString>& accept_types,
|
const std::vector<CefString>& accept_filters,
|
||||||
|
const std::vector<CefString>& accept_extensions,
|
||||||
|
const std::vector<CefString>& accept_descriptions,
|
||||||
CefRefPtr<CefFileDialogCallback> callback) override {
|
CefRefPtr<CefFileDialogCallback> callback) override {
|
||||||
got_onfiledialog_.yes();
|
got_onfiledialog_ct_++;
|
||||||
|
|
||||||
std::string url = browser->GetMainFrame()->GetURL();
|
std::string url = browser->GetMainFrame()->GetURL();
|
||||||
EXPECT_STREQ(kTestUrl, url.c_str());
|
EXPECT_STREQ(kTestUrl, url.c_str());
|
||||||
|
|
||||||
EXPECT_EQ(config_.mode, mode);
|
EXPECT_EQ(config_.mode, mode);
|
||||||
EXPECT_STREQ(config_.title.ToString().c_str(), title.ToString().c_str());
|
EXPECT_STREQ(config_.title.ToString().c_str(), title.ToString().c_str());
|
||||||
EXPECT_STREQ(config_.default_file_name.ToString().c_str(),
|
|
||||||
default_file_name.ToString().c_str());
|
EXPECT_EQ(accept_filters.size(), accept_extensions.size());
|
||||||
TestStringVectorEqual(config_.accept_types, accept_types);
|
EXPECT_EQ(accept_filters.size(), accept_descriptions.size());
|
||||||
|
TestStringVectorEqual(config_.accept_filters, accept_filters);
|
||||||
|
|
||||||
|
if (got_onfiledialog_ct_ == 1U) {
|
||||||
|
// On the 2nd+ call this will be set to the last opened path value
|
||||||
|
// (possibly leftover from a different test).
|
||||||
|
EXPECT_STREQ(config_.default_file_name.ToString().c_str(),
|
||||||
|
default_file_name.ToString().c_str());
|
||||||
|
|
||||||
|
TestStringVectorEqual(config_.accept_extensions, accept_extensions);
|
||||||
|
|
||||||
|
// All descriptions should be empty.
|
||||||
|
for (auto& desc : accept_descriptions) {
|
||||||
|
EXPECT_TRUE(desc.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_.skip_first_callback) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (got_onfiledialog_ct_ == 2U) {
|
||||||
|
// All MIME types should be resolved to file extensions.
|
||||||
|
// A description should be provided for MIME types only.
|
||||||
|
for (size_t i = 0; i < accept_filters.size(); ++i) {
|
||||||
|
const std::string filter = accept_filters[i];
|
||||||
|
const std::string ext = accept_extensions[i];
|
||||||
|
const std::string desc = accept_descriptions[i];
|
||||||
|
if (filter == "image/*") {
|
||||||
|
EXPECT_TRUE(ext.find(".png") > 0);
|
||||||
|
EXPECT_TRUE(ext.find(".jpg") > 0);
|
||||||
|
EXPECT_FALSE(desc.empty());
|
||||||
|
} else {
|
||||||
|
// Single file extensions should match.
|
||||||
|
EXPECT_STREQ(filter.data(), ext.data());
|
||||||
|
EXPECT_TRUE(desc.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NOTREACHED();
|
||||||
|
}
|
||||||
|
|
||||||
if (config_.callback_async) {
|
if (config_.callback_async) {
|
||||||
CefPostTask(TID_UI, base::BindOnce(&DialogTestHandler::ExecuteCallback,
|
CefPostTask(TID_UI, base::BindOnce(&DialogTestHandler::ExecuteCallback,
|
||||||
|
@ -118,15 +165,19 @@ class DialogTestHandler : public TestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DestroyTest() override {
|
void DestroyTest() override {
|
||||||
EXPECT_TRUE(got_onfiledialog_);
|
if (config_.skip_first_callback) {
|
||||||
|
EXPECT_EQ(2U, got_onfiledialog_ct_);
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(1U, got_onfiledialog_ct_);
|
||||||
|
}
|
||||||
EXPECT_TRUE(got_onfiledialogdismissed_);
|
EXPECT_TRUE(got_onfiledialogdismissed_);
|
||||||
|
|
||||||
TestHandler::DestroyTest();
|
TestHandler::DestroyTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
TestConfig config_;
|
const TestConfig& config_;
|
||||||
|
|
||||||
TrackCallback got_onfiledialog_;
|
size_t got_onfiledialog_ct_ = 0;
|
||||||
TrackCallback got_onfiledialogdismissed_;
|
TrackCallback got_onfiledialogdismissed_;
|
||||||
|
|
||||||
IMPLEMENT_REFCOUNTING(DialogTestHandler);
|
IMPLEMENT_REFCOUNTING(DialogTestHandler);
|
||||||
|
@ -139,7 +190,8 @@ TEST(DialogTest, FileEmptyParams) {
|
||||||
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
config.title.clear();
|
config.title.clear();
|
||||||
config.default_file_name.clear();
|
config.default_file_name.clear();
|
||||||
config.accept_types.clear();
|
config.accept_filters.clear();
|
||||||
|
config.accept_extensions.clear();
|
||||||
config.callback_async = false;
|
config.callback_async = false;
|
||||||
config.callback_cancel = false;
|
config.callback_cancel = false;
|
||||||
|
|
||||||
|
@ -159,6 +211,18 @@ TEST(DialogTest, FileOpen) {
|
||||||
ReleaseAndWaitForDestructor(handler);
|
ReleaseAndWaitForDestructor(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DialogTest, FileOpenSkipFirstCallback) {
|
||||||
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
|
config.callback_async = false;
|
||||||
|
config.callback_cancel = false;
|
||||||
|
config.callback_paths.push_back("/path/to/file1.txt");
|
||||||
|
config.skip_first_callback = true;
|
||||||
|
|
||||||
|
CefRefPtr<DialogTestHandler> handler = new DialogTestHandler(config);
|
||||||
|
handler->ExecuteTest();
|
||||||
|
ReleaseAndWaitForDestructor(handler);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DialogTest, FileOpenCancel) {
|
TEST(DialogTest, FileOpenCancel) {
|
||||||
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
config.callback_async = false;
|
config.callback_async = false;
|
||||||
|
@ -169,6 +233,17 @@ TEST(DialogTest, FileOpenCancel) {
|
||||||
ReleaseAndWaitForDestructor(handler);
|
ReleaseAndWaitForDestructor(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DialogTest, FileOpenCancelSkipFirstCallback) {
|
||||||
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
|
config.callback_async = false;
|
||||||
|
config.callback_cancel = true;
|
||||||
|
config.skip_first_callback = true;
|
||||||
|
|
||||||
|
CefRefPtr<DialogTestHandler> handler = new DialogTestHandler(config);
|
||||||
|
handler->ExecuteTest();
|
||||||
|
ReleaseAndWaitForDestructor(handler);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DialogTest, FileOpenAsync) {
|
TEST(DialogTest, FileOpenAsync) {
|
||||||
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
config.callback_async = true;
|
config.callback_async = true;
|
||||||
|
@ -180,9 +255,21 @@ TEST(DialogTest, FileOpenAsync) {
|
||||||
ReleaseAndWaitForDestructor(handler);
|
ReleaseAndWaitForDestructor(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DialogTest, FileOpenAsyncSkipFirstCallback) {
|
||||||
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
|
config.callback_async = true;
|
||||||
|
config.callback_cancel = false;
|
||||||
|
config.callback_paths.push_back("/path/to/file1.txt");
|
||||||
|
config.skip_first_callback = true;
|
||||||
|
|
||||||
|
CefRefPtr<DialogTestHandler> handler = new DialogTestHandler(config);
|
||||||
|
handler->ExecuteTest();
|
||||||
|
ReleaseAndWaitForDestructor(handler);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DialogTest, FileOpenAsyncCancel) {
|
TEST(DialogTest, FileOpenAsyncCancel) {
|
||||||
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
config.callback_async = false;
|
config.callback_async = true;
|
||||||
config.callback_cancel = true;
|
config.callback_cancel = true;
|
||||||
|
|
||||||
CefRefPtr<DialogTestHandler> handler = new DialogTestHandler(config);
|
CefRefPtr<DialogTestHandler> handler = new DialogTestHandler(config);
|
||||||
|
@ -190,6 +277,17 @@ TEST(DialogTest, FileOpenAsyncCancel) {
|
||||||
ReleaseAndWaitForDestructor(handler);
|
ReleaseAndWaitForDestructor(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DialogTest, FileOpenAsyncCancelSkipFirstCallback) {
|
||||||
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN);
|
||||||
|
config.callback_async = true;
|
||||||
|
config.callback_cancel = true;
|
||||||
|
config.skip_first_callback = true;
|
||||||
|
|
||||||
|
CefRefPtr<DialogTestHandler> handler = new DialogTestHandler(config);
|
||||||
|
handler->ExecuteTest();
|
||||||
|
ReleaseAndWaitForDestructor(handler);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DialogTest, FileOpenMultiple) {
|
TEST(DialogTest, FileOpenMultiple) {
|
||||||
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN_MULTIPLE);
|
DialogTestHandler::TestConfig config(FILE_DIALOG_OPEN_MULTIPLE);
|
||||||
config.callback_async = false;
|
config.callback_async = false;
|
||||||
|
|
|
@ -277,6 +277,9 @@ void TestProcessMessageEqual(CefRefPtr<CefProcessMessage> val1,
|
||||||
void TestStringVectorEqual(const std::vector<CefString>& val1,
|
void TestStringVectorEqual(const std::vector<CefString>& val1,
|
||||||
const std::vector<CefString>& val2) {
|
const std::vector<CefString>& val2) {
|
||||||
EXPECT_EQ(val1.size(), val2.size());
|
EXPECT_EQ(val1.size(), val2.size());
|
||||||
|
if (val1.size() != val2.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < val1.size(); ++i) {
|
for (size_t i = 0; i < val1.size(); ++i) {
|
||||||
EXPECT_STREQ(val1[i].ToString().c_str(), val2[i].ToString().c_str());
|
EXPECT_STREQ(val1[i].ToString().c_str(), val2[i].ToString().c_str());
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "tests/shared/common/string_util.h"
|
#include "tests/shared/common/string_util.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace client {
|
namespace client {
|
||||||
|
|
||||||
|
@ -31,4 +33,16 @@ std::string AsciiStrReplace(const std::string& str,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> AsciiStrSplit(const std::string& str, char delim) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::stringstream ss(str);
|
||||||
|
std::string item;
|
||||||
|
|
||||||
|
while (getline(ss, item, delim)) {
|
||||||
|
result.push_back(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace client {
|
namespace client {
|
||||||
|
|
||||||
|
@ -18,6 +19,9 @@ std::string AsciiStrReplace(const std::string& str,
|
||||||
const std::string& from,
|
const std::string& from,
|
||||||
const std::string& to);
|
const std::string& to);
|
||||||
|
|
||||||
|
// Split |str| at character |delim|.
|
||||||
|
std::vector<std::string> AsciiStrSplit(const std::string& str, char delim);
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
|
|
||||||
#endif // CEF_TESTS_SHARED_COMMON_STRING_UTIL_H_
|
#endif // CEF_TESTS_SHARED_COMMON_STRING_UTIL_H_
|
||||||
|
|
Loading…
Reference in New Issue