• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4 
5 #include "tests/cefclient/browser/dialog_handler_gtk.h"
6 
7 #include <libgen.h>
8 #include <sys/stat.h>
9 
10 #include "include/cef_browser.h"
11 #include "include/cef_parser.h"
12 #include "include/wrapper/cef_helpers.h"
13 #include "tests/cefclient/browser/root_window.h"
14 #include "tests/cefclient/browser/util_gtk.h"
15 
16 namespace client {
17 
18 namespace {
19 
20 const char kPromptTextId[] = "cef_prompt_text";
21 
22 // If there's a text entry in the dialog, get the text from the first one and
23 // return it.
GetPromptText(GtkDialog * dialog)24 std::string GetPromptText(GtkDialog* dialog) {
25   GtkWidget* widget = static_cast<GtkWidget*>(
26       g_object_get_data(G_OBJECT(dialog), kPromptTextId));
27   if (widget)
28     return gtk_entry_get_text(GTK_ENTRY(widget));
29   return std::string();
30 }
31 
GetDescriptionFromMimeType(const std::string & mime_type)32 std::string GetDescriptionFromMimeType(const std::string& mime_type) {
33   // Check for wild card mime types and return an appropriate description.
34   static const struct {
35     const char* mime_type;
36     const char* label;
37   } kWildCardMimeTypes[] = {
38       {"audio", "Audio Files"},
39       {"image", "Image Files"},
40       {"text", "Text Files"},
41       {"video", "Video Files"},
42   };
43 
44   for (size_t i = 0;
45        i < sizeof(kWildCardMimeTypes) / sizeof(kWildCardMimeTypes[0]); ++i) {
46     if (mime_type == std::string(kWildCardMimeTypes[i].mime_type) + "/*")
47       return std::string(kWildCardMimeTypes[i].label);
48   }
49 
50   return std::string();
51 }
52 
AddFilters(GtkFileChooser * chooser,const std::vector<CefString> & accept_filters,bool include_all_files,std::vector<GtkFileFilter * > * filters)53 void AddFilters(GtkFileChooser* chooser,
54                 const std::vector<CefString>& accept_filters,
55                 bool include_all_files,
56                 std::vector<GtkFileFilter*>* filters) {
57   bool has_filter = false;
58 
59   for (size_t j = 0; j < accept_filters.size(); ++j) {
60     const std::string& filter = accept_filters[j];
61     if (filter.empty())
62       continue;
63 
64     std::vector<std::string> extensions;
65     std::string description;
66 
67     size_t sep_index = filter.find('|');
68     if (sep_index != std::string::npos) {
69       // Treat as a filter of the form "Filter Name|.ext1;.ext2;.ext3".
70       description = filter.substr(0, sep_index);
71 
72       const std::string& exts = filter.substr(sep_index + 1);
73       size_t last = 0;
74       size_t size = exts.size();
75       for (size_t i = 0; i <= size; ++i) {
76         if (i == size || exts[i] == ';') {
77           std::string ext(exts, last, i - last);
78           if (!ext.empty() && ext[0] == '.')
79             extensions.push_back(ext);
80           last = i + 1;
81         }
82       }
83     } else if (filter[0] == '.') {
84       // Treat as an extension beginning with the '.' character.
85       extensions.push_back(filter);
86     } else {
87       // Otherwise convert mime type to one or more extensions.
88       description = GetDescriptionFromMimeType(filter);
89 
90       std::vector<CefString> ext;
91       CefGetExtensionsForMimeType(filter, ext);
92       for (size_t x = 0; x < ext.size(); ++x)
93         extensions.push_back("." + ext[x].ToString());
94     }
95 
96     if (extensions.empty())
97       continue;
98 
99     GtkFileFilter* gtk_filter = gtk_file_filter_new();
100 
101     std::string ext_str;
102     for (size_t x = 0; x < extensions.size(); ++x) {
103       const std::string& pattern = "*" + extensions[x];
104       if (x != 0)
105         ext_str += ";";
106       ext_str += pattern;
107       gtk_file_filter_add_pattern(gtk_filter, pattern.c_str());
108     }
109 
110     if (description.empty())
111       description = ext_str;
112     else
113       description += " (" + ext_str + ")";
114 
115     gtk_file_filter_set_name(gtk_filter, description.c_str());
116     gtk_file_chooser_add_filter(chooser, gtk_filter);
117     if (!has_filter)
118       has_filter = true;
119 
120     filters->push_back(gtk_filter);
121   }
122 
123   // Add the *.* filter, but only if we have added other filters (otherwise it
124   // is implied).
125   if (include_all_files && has_filter) {
126     GtkFileFilter* filter = gtk_file_filter_new();
127     gtk_file_filter_add_pattern(filter, "*");
128     gtk_file_filter_set_name(filter, "All Files (*)");
129     gtk_file_chooser_add_filter(chooser, filter);
130   }
131 }
132 
GetWindow(CefRefPtr<CefBrowser> browser)133 GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
134   REQUIRE_MAIN_THREAD();
135   scoped_refptr<RootWindow> root_window =
136       RootWindow::GetForBrowser(browser->GetIdentifier());
137   if (root_window) {
138     GtkWidget* window = root_window->GetWindowHandle();
139     if (!window)
140       LOG(ERROR) << "No GtkWindow for browser";
141     return GTK_WINDOW(window);
142   }
143   return nullptr;
144 }
145 
146 }  // namespace
147 
ClientDialogHandlerGtk()148 ClientDialogHandlerGtk::ClientDialogHandlerGtk() : gtk_dialog_(nullptr) {}
149 
OnFileDialog(CefRefPtr<CefBrowser> browser,FileDialogMode mode,const CefString & title,const CefString & default_file_path,const std::vector<CefString> & accept_filters,int selected_accept_filter,CefRefPtr<CefFileDialogCallback> callback)150 bool ClientDialogHandlerGtk::OnFileDialog(
151     CefRefPtr<CefBrowser> browser,
152     FileDialogMode mode,
153     const CefString& title,
154     const CefString& default_file_path,
155     const std::vector<CefString>& accept_filters,
156     int selected_accept_filter,
157     CefRefPtr<CefFileDialogCallback> callback) {
158   CEF_REQUIRE_UI_THREAD();
159 
160   OnFileDialogParams params;
161   params.browser = browser;
162   params.mode = mode;
163   params.title = title;
164   params.default_file_path = default_file_path;
165   params.accept_filters = accept_filters;
166   params.selected_accept_filter = selected_accept_filter;
167   params.callback = callback;
168 
169   GetWindowAndContinue(
170       browser, base::BindOnce(&ClientDialogHandlerGtk::OnFileDialogContinue,
171                               this, params));
172   return true;
173 }
174 
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)175 bool ClientDialogHandlerGtk::OnJSDialog(CefRefPtr<CefBrowser> browser,
176                                         const CefString& origin_url,
177                                         JSDialogType dialog_type,
178                                         const CefString& message_text,
179                                         const CefString& default_prompt_text,
180                                         CefRefPtr<CefJSDialogCallback> callback,
181                                         bool& suppress_message) {
182   CEF_REQUIRE_UI_THREAD();
183 
184   OnJSDialogParams params;
185   params.browser = browser;
186   params.origin_url = origin_url;
187   params.dialog_type = dialog_type;
188   params.message_text = message_text;
189   params.default_prompt_text = default_prompt_text;
190   params.callback = callback;
191 
192   GetWindowAndContinue(
193       browser, base::BindOnce(&ClientDialogHandlerGtk::OnJSDialogContinue, this,
194                               params));
195   return true;
196 }
197 
OnBeforeUnloadDialog(CefRefPtr<CefBrowser> browser,const CefString & message_text,bool is_reload,CefRefPtr<CefJSDialogCallback> callback)198 bool ClientDialogHandlerGtk::OnBeforeUnloadDialog(
199     CefRefPtr<CefBrowser> browser,
200     const CefString& message_text,
201     bool is_reload,
202     CefRefPtr<CefJSDialogCallback> callback) {
203   CEF_REQUIRE_UI_THREAD();
204 
205   const std::string& new_message_text =
206       message_text.ToString() + "\n\nIs it OK to leave/reload this page?";
207   bool suppress_message = false;
208 
209   return OnJSDialog(browser, CefString(), JSDIALOGTYPE_CONFIRM,
210                     new_message_text, CefString(), callback, suppress_message);
211 }
212 
OnResetDialogState(CefRefPtr<CefBrowser> browser)213 void ClientDialogHandlerGtk::OnResetDialogState(CefRefPtr<CefBrowser> browser) {
214   CEF_REQUIRE_UI_THREAD();
215 
216   if (!gtk_dialog_)
217     return;
218 
219   gtk_widget_destroy(gtk_dialog_);
220   gtk_dialog_ = nullptr;
221   js_dialog_callback_ = nullptr;
222 }
223 
OnFileDialogContinue(OnFileDialogParams params,GtkWindow * window)224 void ClientDialogHandlerGtk::OnFileDialogContinue(OnFileDialogParams params,
225                                                   GtkWindow* window) {
226   REQUIRE_MAIN_THREAD();
227 
228   ScopedGdkThreadsEnter scoped_gdk_threads;
229 
230   std::vector<CefString> files;
231 
232   GtkFileChooserAction action;
233   const gchar* accept_button;
234 
235   // Remove any modifier flags.
236   FileDialogMode mode_type =
237       static_cast<FileDialogMode>(params.mode & FILE_DIALOG_TYPE_MASK);
238 
239   if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_MULTIPLE) {
240     action = GTK_FILE_CHOOSER_ACTION_OPEN;
241     accept_button = "_Open";
242   } else if (mode_type == FILE_DIALOG_OPEN_FOLDER) {
243     action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
244     accept_button = "_Open";
245   } else if (mode_type == FILE_DIALOG_SAVE) {
246     action = GTK_FILE_CHOOSER_ACTION_SAVE;
247     accept_button = "_Save";
248   } else {
249     NOTREACHED();
250     params.callback->Cancel();
251     return;
252   }
253 
254   std::string title_str;
255   if (!params.title.empty()) {
256     title_str = params.title;
257   } else {
258     switch (mode_type) {
259       case FILE_DIALOG_OPEN:
260         title_str = "Open File";
261         break;
262       case FILE_DIALOG_OPEN_MULTIPLE:
263         title_str = "Open Files";
264         break;
265       case FILE_DIALOG_OPEN_FOLDER:
266         title_str = "Open Folder";
267         break;
268       case FILE_DIALOG_SAVE:
269         title_str = "Save File";
270         break;
271       default:
272         break;
273     }
274   }
275 
276   GtkWidget* dialog = gtk_file_chooser_dialog_new(
277       title_str.c_str(), GTK_WINDOW(window), action, "_Cancel",
278       GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, nullptr);
279 
280   if (mode_type == FILE_DIALOG_OPEN_MULTIPLE)
281     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
282 
283   if (mode_type == FILE_DIALOG_SAVE) {
284     gtk_file_chooser_set_do_overwrite_confirmation(
285         GTK_FILE_CHOOSER(dialog),
286         !!(params.mode & FILE_DIALOG_OVERWRITEPROMPT_FLAG));
287   }
288 
289   gtk_file_chooser_set_show_hidden(
290       GTK_FILE_CHOOSER(dialog), !(params.mode & FILE_DIALOG_HIDEREADONLY_FLAG));
291 
292   if (!params.default_file_path.empty() && mode_type == FILE_DIALOG_SAVE) {
293     const std::string& file_path = params.default_file_path;
294     bool exists = false;
295 
296     struct stat sb;
297     if (stat(file_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
298       // Use the directory and name of the existing file.
299       gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), file_path.data());
300       exists = true;
301     }
302 
303     if (!exists) {
304       // Set the current file name but let the user choose the directory.
305       std::string file_name_str = file_path;
306       const char* file_name = basename(const_cast<char*>(file_name_str.data()));
307       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), file_name);
308     }
309   }
310 
311   std::vector<GtkFileFilter*> filters;
312   AddFilters(GTK_FILE_CHOOSER(dialog), params.accept_filters, true, &filters);
313   if (params.selected_accept_filter < static_cast<int>(filters.size())) {
314     gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog),
315                                 filters[params.selected_accept_filter]);
316   }
317 
318   bool success = false;
319 
320   if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
321     if (mode_type == FILE_DIALOG_OPEN || mode_type == FILE_DIALOG_OPEN_FOLDER ||
322         mode_type == FILE_DIALOG_SAVE) {
323       char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
324       files.push_back(std::string(filename));
325       success = true;
326     } else if (mode_type == FILE_DIALOG_OPEN_MULTIPLE) {
327       GSList* filenames =
328           gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
329       if (filenames) {
330         for (GSList* iter = filenames; iter != nullptr;
331              iter = g_slist_next(iter)) {
332           std::string path(static_cast<char*>(iter->data));
333           g_free(iter->data);
334           files.push_back(path);
335         }
336         g_slist_free(filenames);
337         success = true;
338       }
339     }
340   }
341 
342   int filter_index = params.selected_accept_filter;
343   if (success) {
344     GtkFileFilter* selected_filter =
345         gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
346     if (selected_filter != nullptr) {
347       for (size_t x = 0; x < filters.size(); ++x) {
348         if (filters[x] == selected_filter) {
349           filter_index = x;
350           break;
351         }
352       }
353     }
354   }
355 
356   gtk_widget_destroy(dialog);
357 
358   if (success)
359     params.callback->Continue(filter_index, files);
360   else
361     params.callback->Cancel();
362 }
363 
OnJSDialogContinue(OnJSDialogParams params,GtkWindow * window)364 void ClientDialogHandlerGtk::OnJSDialogContinue(OnJSDialogParams params,
365                                                 GtkWindow* window) {
366   REQUIRE_MAIN_THREAD();
367 
368   ScopedGdkThreadsEnter scoped_gdk_threads;
369 
370   GtkButtonsType buttons = GTK_BUTTONS_NONE;
371   GtkMessageType gtk_message_type = GTK_MESSAGE_OTHER;
372   std::string title;
373 
374   switch (params.dialog_type) {
375     case JSDIALOGTYPE_ALERT:
376       buttons = GTK_BUTTONS_NONE;
377       gtk_message_type = GTK_MESSAGE_WARNING;
378       title = "JavaScript Alert";
379       break;
380 
381     case JSDIALOGTYPE_CONFIRM:
382       buttons = GTK_BUTTONS_CANCEL;
383       gtk_message_type = GTK_MESSAGE_QUESTION;
384       title = "JavaScript Confirm";
385       break;
386 
387     case JSDIALOGTYPE_PROMPT:
388       buttons = GTK_BUTTONS_CANCEL;
389       gtk_message_type = GTK_MESSAGE_QUESTION;
390       title = "JavaScript Prompt";
391       break;
392   }
393 
394   js_dialog_callback_ = params.callback;
395 
396   if (!params.origin_url.empty()) {
397     title += " - ";
398     title += CefFormatUrlForSecurityDisplay(params.origin_url).ToString();
399   }
400 
401   gtk_dialog_ = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
402                                        gtk_message_type, buttons, "%s",
403                                        params.message_text.ToString().c_str());
404   g_signal_connect(gtk_dialog_, "delete-event",
405                    G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
406 
407   gtk_window_set_title(GTK_WINDOW(gtk_dialog_), title.c_str());
408 
409   GtkWidget* ok_button =
410       gtk_dialog_add_button(GTK_DIALOG(gtk_dialog_), "_OK", GTK_RESPONSE_OK);
411 
412   if (params.dialog_type != JSDIALOGTYPE_PROMPT)
413     gtk_widget_grab_focus(ok_button);
414 
415   if (params.dialog_type == JSDIALOGTYPE_PROMPT) {
416     GtkWidget* content_area =
417         gtk_dialog_get_content_area(GTK_DIALOG(gtk_dialog_));
418     GtkWidget* text_box = gtk_entry_new();
419     gtk_entry_set_text(GTK_ENTRY(text_box),
420                        params.default_prompt_text.ToString().c_str());
421     gtk_box_pack_start(GTK_BOX(content_area), text_box, TRUE, TRUE, 0);
422     g_object_set_data(G_OBJECT(gtk_dialog_), kPromptTextId, text_box);
423     gtk_entry_set_activates_default(GTK_ENTRY(text_box), TRUE);
424   }
425 
426   gtk_dialog_set_default_response(GTK_DIALOG(gtk_dialog_), GTK_RESPONSE_OK);
427   g_signal_connect(gtk_dialog_, "response", G_CALLBACK(OnDialogResponse), this);
428   gtk_widget_show_all(GTK_WIDGET(gtk_dialog_));
429 }
430 
GetWindowAndContinue(CefRefPtr<CefBrowser> browser,base::OnceCallback<void (GtkWindow *)> callback)431 void ClientDialogHandlerGtk::GetWindowAndContinue(
432     CefRefPtr<CefBrowser> browser,
433     base::OnceCallback<void(GtkWindow*)> callback) {
434   if (!CURRENTLY_ON_MAIN_THREAD()) {
435     MAIN_POST_CLOSURE(
436         base::BindOnce(&ClientDialogHandlerGtk::GetWindowAndContinue, this,
437                        browser, std::move(callback)));
438     return;
439   }
440 
441   GtkWindow* window = GetWindow(browser);
442   if (window) {
443     std::move(callback).Run(window);
444   }
445 }
446 
447 // static
OnDialogResponse(GtkDialog * dialog,gint response_id,ClientDialogHandlerGtk * handler)448 void ClientDialogHandlerGtk::OnDialogResponse(GtkDialog* dialog,
449                                               gint response_id,
450                                               ClientDialogHandlerGtk* handler) {
451   REQUIRE_MAIN_THREAD();
452 
453   DCHECK_EQ(dialog, GTK_DIALOG(handler->gtk_dialog_));
454   switch (response_id) {
455     case GTK_RESPONSE_OK:
456       handler->js_dialog_callback_->Continue(true, GetPromptText(dialog));
457       break;
458     case GTK_RESPONSE_CANCEL:
459     case GTK_RESPONSE_DELETE_EVENT:
460       handler->js_dialog_callback_->Continue(false, CefString());
461       break;
462     default:
463       NOTREACHED();
464   }
465 
466   CefPostTask(TID_UI,
467               base::BindOnce(&ClientDialogHandlerGtk::OnResetDialogState,
468                              handler, nullptr));
469 }
470 
471 }  // namespace client
472