diff options
author | Inna Palant <ipalant@google.com> | 2023-12-12 19:56:22 -0800 |
---|---|---|
committer | Inna Palant <ipalant@google.com> | 2023-12-12 19:56:22 -0800 |
commit | 1ee1f36e3b492d7a6a0bf2367fe509365853aeaa (patch) | |
tree | a5790e155ff0d998639beb6172dfa0feb294aac9 /tests/cefclient/browser/dialog_handler_gtk.cc | |
parent | 1e76199e6f0ded437fca14eb9e512e0c6bd0fc1e (diff) | |
parent | c33b736614ef77e4048cee551999a656a5ef465f (diff) | |
download | cef-1ee1f36e3b492d7a6a0bf2367fe509365853aeaa.tar.gz |
Merge remote-tracking branch 'origin/upstream'main
Import b/312293934
Diffstat (limited to 'tests/cefclient/browser/dialog_handler_gtk.cc')
-rw-r--r-- | tests/cefclient/browser/dialog_handler_gtk.cc | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/tests/cefclient/browser/dialog_handler_gtk.cc b/tests/cefclient/browser/dialog_handler_gtk.cc new file mode 100644 index 00000000..33e687bb --- /dev/null +++ b/tests/cefclient/browser/dialog_handler_gtk.cc @@ -0,0 +1,456 @@ +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include "tests/cefclient/browser/dialog_handler_gtk.h" + +#include <libgen.h> +#include <sys/stat.h> + +#include "include/cef_browser.h" +#include "include/cef_parser.h" +#include "include/wrapper/cef_helpers.h" +#include "tests/cefclient/browser/root_window.h" +#include "tests/cefclient/browser/util_gtk.h" + +namespace client { + +namespace { + +const char kPromptTextId[] = "cef_prompt_text"; + +// If there's a text entry in the dialog, get the text from the first one and +// return it. +std::string GetPromptText(GtkDialog* dialog) { + GtkWidget* widget = static_cast<GtkWidget*>( + g_object_get_data(G_OBJECT(dialog), kPromptTextId)); + if (widget) { + return gtk_entry_get_text(GTK_ENTRY(widget)); + } + return std::string(); +} + +std::string GetDescriptionFromMimeType(const std::string& mime_type) { + // Check for wild card mime types and return an appropriate description. + static const struct { + const char* mime_type; + const char* label; + } kWildCardMimeTypes[] = { + {"audio", "Audio Files"}, + {"image", "Image Files"}, + {"text", "Text Files"}, + {"video", "Video Files"}, + }; + + for (size_t i = 0; + i < sizeof(kWildCardMimeTypes) / sizeof(kWildCardMimeTypes[0]); ++i) { + if (mime_type == std::string(kWildCardMimeTypes[i].mime_type) + "/*") { + return std::string(kWildCardMimeTypes[i].label); + } + } + + return std::string(); +} + +void AddFilters(GtkFileChooser* chooser, + const std::vector<CefString>& accept_filters, + bool include_all_files, + std::vector<GtkFileFilter*>* filters) { + bool has_filter = false; + + for (size_t j = 0; j < accept_filters.size(); ++j) { + const std::string& filter = accept_filters[j]; + if (filter.empty()) { + continue; + } + + std::vector<std::string> extensions; + std::string 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); + + 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<CefString> 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 + // is implied). + 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_chooser_add_filter(chooser, filter); + } +} + +GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) { + REQUIRE_MAIN_THREAD(); + scoped_refptr<RootWindow> root_window = + RootWindow::GetForBrowser(browser->GetIdentifier()); + if (root_window) { + GtkWidget* window = root_window->GetWindowHandle(); + DCHECK(window); + if (!window) { + LOG(ERROR) << "No GtkWindow for browser"; + } + return GTK_WINDOW(window); + } + return nullptr; +} + +} // namespace + +ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(nullptr) {} + +bool ClientDialogHandlerGtk::OnFileDialog( + CefRefPtr<CefBrowser> browser, + FileDialogMode mode, + const CefString& title, + const CefString& default_file_path, + const std::vector<CefString>& accept_filters, + CefRefPtr<CefFileDialogCallback> callback) { + CEF_REQUIRE_UI_THREAD(); + + OnFileDialogParams params; + params.browser = browser; + params.mode = mode; + params.title = title; + params.default_file_path = default_file_path; + params.accept_filters = accept_filters; + params.callback = callback; + + GetWindowAndContinue( + browser, base::BindOnce(&ClientDialogHandlerGtk::OnFileDialogContinue, + this, params)); + return true; +} + +bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser, + const CefString& origin_url, + JSDialogType dialog_type, + const CefString& message_text, + const CefString& default_prompt_text, + CefRefPtr<CefJSDialogCallback> callback, + bool& suppress_message) { + CEF_REQUIRE_UI_THREAD(); + + OnJSDialogParams params; + params.browser = browser; + params.origin_url = origin_url; + params.dialog_type = dialog_type; + params.message_text = message_text; + params.default_prompt_text = default_prompt_text; + params.callback = callback; + + GetWindowAndContinue( + browser, base::BindOnce(&ClientDialogHandlerGtk::OnJSDialogContinue, this, + params)); + return true; +} + +bool ClientDialogHandlerGtk::OnBeforeUnloadDialog( + CefRefPtr<CefBrowser> browser, + const CefString& message_text, + bool is_reload, + CefRefPtr<CefJSDialogCallback> callback) { + CEF_REQUIRE_UI_THREAD(); + + const std::string& new_message_text = + message_text.ToString() + "\n\nIs it OK to leave/reload this page?"; + bool suppress_message = false; + + return OnJSDialog(browser, CefString(), JSDIALOGTYPE_CONFIRM, + new_message_text, CefString(), callback, suppress_message); +} + +void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr<CefBrowser> browser) { + CEF_REQUIRE_UI_THREAD(); + + if (!gtk_dialog_) { + return; + } + + gtk_widget_destroy(gtk_dialog_); + gtk_dialog_ = nullptr; + js_dialog_callback_ = nullptr; +} + +void ClientDialogHandlerGtk::OnFileDialogContinue( + const OnFileDialogParams& params, + GtkWindow* window) { + REQUIRE_MAIN_THREAD(); + + ScopedGdkThreadsEnter scoped_gdk_threads; + + std::vector<CefString> files; + + GtkFileChooserAction action; + const gchar* accept_button; + + if (params.mode == FILE_DIALOG_OPEN || + params.mode == FILE_DIALOG_OPEN_MULTIPLE) { + action = GTK_FILE_CHOOSER_ACTION_OPEN; + accept_button = "_Open"; + } else if (params.mode == FILE_DIALOG_OPEN_FOLDER) { + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + accept_button = "_Open"; + } else if (params.mode == FILE_DIALOG_SAVE) { + action = GTK_FILE_CHOOSER_ACTION_SAVE; + accept_button = "_Save"; + } else { + NOTREACHED(); + params.callback->Cancel(); + return; + } + + std::string title_str; + if (!params.title.empty()) { + title_str = params.title; + } else { + switch (params.mode) { + case FILE_DIALOG_OPEN: + title_str = "Open File"; + break; + 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; + default: + break; + } + } + + GtkWidget* dialog = gtk_file_chooser_dialog_new( + title_str.c_str(), GTK_WINDOW(window), action, "_Cancel", + GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, nullptr); + + if (params.mode == FILE_DIALOG_OPEN_MULTIPLE) { + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + } + + if (!params.default_file_path.empty() && params.mode == FILE_DIALOG_SAVE) { + const std::string& file_path = params.default_file_path; + bool exists = false; + + struct stat sb; + if (stat(file_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) { + // Use the directory and name of the existing file. + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), file_path.data()); + exists = true; + } + + if (!exists) { + // Set the current file name but let the user choose the directory. + std::string file_name_str = file_path; + const char* file_name = basename(const_cast<char*>(file_name_str.data())); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), file_name); + } + } + + std::vector<GtkFileFilter*> filters; + AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters, true, &filters); + + bool success = false; + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + if (params.mode == FILE_DIALOG_OPEN || + params.mode == FILE_DIALOG_OPEN_FOLDER || + params.mode == FILE_DIALOG_SAVE) { + char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + files.push_back(std::string(filename)); + success = true; + } else if (params.mode == FILE_DIALOG_OPEN_MULTIPLE) { + GSList* filenames = + gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + if (filenames) { + for (GSList* iter = filenames; iter != nullptr; + iter = g_slist_next(iter)) { + std::string path(static_cast<char*>(iter->data)); + g_free(iter->data); + files.push_back(path); + } + g_slist_free(filenames); + success = true; + } + } + } + + gtk_widget_destroy(dialog); + + if (success) { + params.callback->Continue(files); + } else { + params.callback->Cancel(); + } +} + +void ClientDialogHandlerGtk::OnJSDialogContinue(const OnJSDialogParams& params, + GtkWindow* window) { + REQUIRE_MAIN_THREAD(); + + ScopedGdkThreadsEnter scoped_gdk_threads; + + GtkButtonsType buttons = GTK_BUTTONS_NONE; + GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER; + std::string title; + + switch (params.dialog_type) { + case JSDIALOGTYPE_ALERT: + buttons = GTK_BUTTONS_NONE; + gtk_message_type = GTK_MESSAGE_WARNING; + title = "JavaScript Alert"; + break; + + case JSDIALOGTYPE_CONFIRM: + buttons = GTK_BUTTONS_CANCEL; + gtk_message_type = GTK_MESSAGE_QUESTION; + title = "JavaScript Confirm"; + break; + + case JSDIALOGTYPE_PROMPT: + buttons = GTK_BUTTONS_CANCEL; + gtk_message_type = GTK_MESSAGE_QUESTION; + title = "JavaScript Prompt"; + break; + } + + js_dialog_callback_ = params.callback; + + if (!params.origin_url.empty()) { + title += " - "; + title += CefFormatUrlForSecurityDisplay(params.origin_url).ToString(); + } + + gtk_dialog_ = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, + gtk_message_type, buttons, "%s", + params.message_text.ToString().c_str()); + g_signal_connect(gtk_dialog_, "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), nullptr); + + gtk_window_set_title(GTK_WINDOW(gtk_dialog_), title.c_str()); + + GtkWidget* ok_button = + gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_), "_OK", GTK_RESPONSE_OK); + + if (params.dialog_type != JSDIALOGTYPE_PROMPT) { + gtk_widget_grab_focus(ok_button); + } + + if (params.dialog_type == JSDIALOGTYPE_PROMPT) { + GtkWidget* content_area = + gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_)); + GtkWidget* text_box = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(text_box), + params.default_prompt_text.ToString().c_str()); + gtk_box_pack_start(GTK_BOX(content_area), text_box, TRUE, TRUE, 0); + g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box); + gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE); + } + + gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK); + g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this); + gtk_widget_show_all(GTK_WIDGET(gtk_dialog_)); +} + +void ClientDialogHandlerGtk::GetWindowAndContinue( + CefRefPtr<CefBrowser> browser, + base::OnceCallback<void(GtkWindow*)> callback) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + MAIN_POST_CLOSURE( + base::BindOnce(&ClientDialogHandlerGtk::GetWindowAndContinue, this, + browser, std::move(callback))); + return; + } + + GtkWindow* window = GetWindow(browser); + if (window) { + std::move(callback).Run(window); + } +} + +// static +void ClientDialogHandlerGtk::OnDialogResponse(GtkDialog* dialog, + gint response_id, + ClientDialogHandlerGtk* handler) { + REQUIRE_MAIN_THREAD(); + + DCHECK_EQ(dialog, GTK_DIALOG(handler->gtk_dialog_)); + switch (response_id) { + case GTK_RESPONSE_OK: + handler->js_dialog_callback_->Continue(true, GetPromptText(dialog)); + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + handler->js_dialog_callback_->Continue(false, CefString()); + break; + default: + NOTREACHED(); + } + + CefPostTask(TID_UI, + base::BindOnce(&ClientDialogHandlerGtk::OnResetDialogState, + handler, nullptr)); +} + +} // namespace client |