// 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/base/cef_logging.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" #include "tests/shared/common/string_util.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); } } LOG(WARNING) << "Unrecognized mime type: " << mime_type; return std::string(); } void AddFilters(GtkFileChooser* chooser, const std::vector& accept_filters, const std::vector& accept_extensions, const std::vector& accept_descriptions, bool include_all_files, std::vector* 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; } // Extensions and descriptions may be provided. std::vector extensions = AsciiStrSplit(accept_extensions[j], ';'); std::string description = accept_descriptions[j]; if (extensions.empty()) { // Try to convert mime type to one or more extensions. 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; } if (description.empty() && filter[0] != '.') { description = GetDescriptionFromMimeType(filter); } 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(); DCHECK(window); if (!window) { LOG(ERROR) << "No GtkWindow for browser"; } return GTK_WINDOW(window); } return nullptr; } // Returns true if |accept_filters| contains a MIME type value without matching // extensions in |accept_extensions|. bool MissingMimeTypeData(const std::vector& accept_filters, const std::vector& 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 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, const std::vector& accept_extensions, const std::vector& accept_descriptions, CefRefPtr callback) { 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; params.browser = browser; params.mode = mode; params.title = title; params.default_file_path = default_file_path; params.accept_filters = accept_filters; params.accept_extensions = accept_extensions; params.accept_descriptions = accept_descriptions; 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( const OnFileDialogParams& params, GtkWindow* window) { REQUIRE_MAIN_THREAD(); ScopedGdkThreadsEnter scoped_gdk_threads; std::vector 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(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, params.accept_extensions, params.accept_descriptions, 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(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 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