// 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 #include #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( 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& accept_filters, bool include_all_files, std::vector* filters) { bool has_filter = false; for (size_t i = 0; i < accept_filters.size(); ++i) { const std::string& filter = accept_filters[i]; if (filter.empty()) continue; std::vector 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 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 browser) { REQUIRE_MAIN_THREAD(); scoped_refptr root_window = RootWindow::GetForBrowser(browser->GetIdentifier()); if (root_window) { GtkWidget* window = root_window->GetWindowHandle(); if (!window) LOG(ERROR) << "No GtkWindow for browser"; return GTK_WINDOW(window); } return nullptr; } } // namespace ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(nullptr) {} bool ClientDialogHandlerGtk::OnFileDialog( CefRefPtr browser, FileDialogMode mode, const CefString& title, const CefString& default_file_path, const std::vector& accept_filters, int selected_accept_filter, CefRefPtr 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.selected_accept_filter = selected_accept_filter; params.callback = callback; GetWindowAndContinue( browser, base::BindOnce(&ClientDialogHandlerGtk::OnFileDialogContinue, this, params)); return true; } bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr browser, const CefString& origin_url, JSDialogType dialog_type, const CefString& message_text, const CefString& default_prompt_text, CefRefPtr 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 browser, const CefString& message_text, bool is_reload, CefRefPtr 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 browser) { CEF_REQUIRE_UI_THREAD(); if (!gtk_dialog_) return; gtk_widget_destroy(gtk_dialog_); gtk_dialog_ = nullptr; js_dialog_callback_ = nullptr; } void ClientDialogHandlerGtk::OnFileDialogContinue(OnFileDialogParams params, GtkWindow* window) { REQUIRE_MAIN_THREAD(); ScopedGdkThreadsEnter scoped_gdk_threads; std::vector files; GtkFileChooserAction action; const gchar* accept_button; // Remove any modifier flags. FileDialogMode mode_type = static_cast(params.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 = "_Open"; } else if (mode_type == FILE_DIALOG_OPEN_FOLDER) { action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; accept_button = "_Open"; } else if (mode_type == 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 (mode_type) { 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 (mode_type == FILE_DIALOG_OPEN_MULTIPLE) gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); if (mode_type == FILE_DIALOG_SAVE) { gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), !!(params.mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG)); } gtk_file_chooser_set_show_hidden( GTK_FILE_CHOOSER(dialog), !(params.mode & FILE_DIALOG_HIDEREADONLY_FLAG)); if (!params.default_file_path.empty() && mode_type == 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(file_name_str.data())); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), file_name); } } std::vector filters; AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters, true, &filters); if (params.selected_accept_filter < static_cast(filters.size())) { gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filters[params.selected_accept_filter]); } bool success = false; if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { 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_type == 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(iter->data)); g_free(iter->data); files.push_back(path); } g_slist_free(filenames); success = true; } } } int filter_index = params.selected_accept_filter; if (success) { GtkFileFilter* selected_filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); if (selected_filter != nullptr) { for (size_t x = 0; x < filters.size(); ++x) { if (filters[x] == selected_filter) { filter_index = x; break; } } } } gtk_widget_destroy(dialog); if (success) params.callback->Continue(filter_index, files); else params.callback->Cancel(); } void ClientDialogHandlerGtk::OnJSDialogContinue(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 browser, base::OnceCallback 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