1/*
2 * Copyright (C) 2017 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitWebView.h"
22
23#include "WebKitAuthenticationDialog.h"
24#include "WebKitScriptDialogImpl.h"
25#include "WebKitWebViewBasePrivate.h"
26#include "WebKitWebViewPrivate.h"
27#include <WebCore/Color.h>
28#include <WebCore/GtkUtilities.h>
29#include <WebCore/PlatformDisplay.h>
30#include <WebCore/PlatformScreen.h>
31#include <glib/gi18n-lib.h>
32#include <gtk/gtk.h>
33
34gboolean webkitWebViewAuthenticate(WebKitWebView* webView, WebKitAuthenticationRequest* request)
35{
36 CredentialStorageMode credentialStorageMode = webkit_authentication_request_can_save_credentials(request) ? AllowPersistentStorage : DisallowPersistentStorage;
37 webkitWebViewBaseAddDialog(WEBKIT_WEB_VIEW_BASE(webView), webkitAuthenticationDialogNew(request, credentialStorageMode));
38
39 return TRUE;
40}
41
42gboolean webkitWebViewScriptDialog(WebKitWebView* webView, WebKitScriptDialog* scriptDialog)
43{
44 GUniquePtr<char> title(g_strdup_printf("JavaScript - %s", webkitWebViewGetPage(webView).pageLoadState().url().utf8().data()));
45 // Limit script dialog size to 80% of the web view size.
46 GtkRequisition maxSize = { static_cast<int>(gtk_widget_get_allocated_width(GTK_WIDGET(webView)) * 0.80), static_cast<int>(gtk_widget_get_allocated_height(GTK_WIDGET(webView)) * 0.80) };
47 webkitWebViewBaseAddDialog(WEBKIT_WEB_VIEW_BASE(webView), webkitScriptDialogImplNew(scriptDialog, title.get(), &maxSize));
48
49 return TRUE;
50}
51
52static void fileChooserDialogResponseCallback(GtkFileChooser* dialog, gint responseID, WebKitFileChooserRequest* request)
53{
54 GRefPtr<WebKitFileChooserRequest> adoptedRequest = adoptGRef(request);
55 if (responseID == GTK_RESPONSE_ACCEPT) {
56 GUniquePtr<GSList> filesList(gtk_file_chooser_get_filenames(dialog));
57 GRefPtr<GPtrArray> filesArray = adoptGRef(g_ptr_array_new());
58 for (GSList* file = filesList.get(); file; file = g_slist_next(file))
59 g_ptr_array_add(filesArray.get(), file->data);
60 g_ptr_array_add(filesArray.get(), 0);
61 webkit_file_chooser_request_select_files(adoptedRequest.get(), reinterpret_cast<const gchar* const*>(filesArray->pdata));
62 } else
63 webkit_file_chooser_request_cancel(adoptedRequest.get());
64
65#if GTK_CHECK_VERSION(3, 20, 0)
66 g_object_unref(dialog);
67#else
68 gtk_widget_destroy(GTK_WIDGET(dialog));
69#endif
70}
71
72gboolean webkitWebViewRunFileChooser(WebKitWebView* webView, WebKitFileChooserRequest* request)
73{
74 GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(webView));
75 if (!WebCore::widgetIsOnscreenToplevelWindow(toplevel))
76 toplevel = 0;
77
78 gboolean allowsMultipleSelection = webkit_file_chooser_request_get_select_multiple(request);
79
80#if GTK_CHECK_VERSION(3, 20, 0)
81 GtkFileChooserNative* dialog = gtk_file_chooser_native_new(allowsMultipleSelection ? _("Select Files") : _("Select File"),
82 toplevel ? GTK_WINDOW(toplevel) : nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr);
83 if (toplevel)
84 gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(dialog), TRUE);
85#else
86 GtkWidget* dialog = gtk_file_chooser_dialog_new(allowsMultipleSelection ? _("Select Files") : _("Select File"),
87 toplevel ? GTK_WINDOW(toplevel) : nullptr,
88 GTK_FILE_CHOOSER_ACTION_OPEN,
89 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
90 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
91 nullptr);
92 if (toplevel)
93 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
94#endif
95
96 if (GtkFileFilter* filter = webkit_file_chooser_request_get_mime_types_filter(request))
97 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
98 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), allowsMultipleSelection);
99
100 if (const gchar* const* selectedFiles = webkit_file_chooser_request_get_selected_files(request))
101 gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(dialog), selectedFiles[0]);
102
103 g_signal_connect(dialog, "response", G_CALLBACK(fileChooserDialogResponseCallback), g_object_ref(request));
104
105#if GTK_CHECK_VERSION(3, 20, 0)
106 gtk_native_dialog_show(GTK_NATIVE_DIALOG(dialog));
107#else
108 gtk_widget_show(dialog);
109#endif
110
111 return TRUE;
112}
113
114struct WindowStateEvent {
115 enum class Type { Maximize, Minimize, Restore };
116
117 WindowStateEvent(Type type, CompletionHandler<void()>&& completionHandler)
118 : type(type)
119 , completionHandler(WTFMove(completionHandler))
120 , completeTimer(RunLoop::main(), this, &WindowStateEvent::complete)
121 {
122 // Complete the event if not done after one second.
123 completeTimer.startOneShot(1_s);
124 }
125
126 ~WindowStateEvent()
127 {
128 complete();
129 }
130
131 void complete()
132 {
133 if (auto handler = std::exchange(completionHandler, nullptr))
134 handler();
135 }
136
137 Type type;
138 CompletionHandler<void()> completionHandler;
139 RunLoop::Timer<WindowStateEvent> completeTimer;
140};
141
142static const char* gWindowStateEventID = "wk-window-state-event";
143
144static gboolean windowStateEventCallback(GtkWidget* window, GdkEventWindowState* event, WebKitWebView* view)
145{
146 auto* state = static_cast<WindowStateEvent*>(g_object_get_data(G_OBJECT(view), gWindowStateEventID));
147 if (!state) {
148 g_signal_handlers_disconnect_by_func(window, reinterpret_cast<gpointer>(windowStateEventCallback), view);
149 return FALSE;
150 }
151
152 bool eventCompleted = false;
153 switch (state->type) {
154 case WindowStateEvent::Type::Maximize:
155 if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
156 eventCompleted = true;
157 break;
158 case WindowStateEvent::Type::Minimize:
159 if ((event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) || !gtk_widget_get_mapped(window))
160 eventCompleted = true;
161 break;
162 case WindowStateEvent::Type::Restore:
163 if (!(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) && !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
164 eventCompleted = true;
165 break;
166 }
167
168 if (eventCompleted) {
169 g_signal_handlers_disconnect_by_func(window, reinterpret_cast<gpointer>(windowStateEventCallback), view);
170 g_object_set_data(G_OBJECT(view), gWindowStateEventID, nullptr);
171 }
172
173 return FALSE;
174}
175
176void webkitWebViewMaximizeWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
177{
178 auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
179 if (!gtk_widget_is_toplevel(topLevel)) {
180 completionHandler();
181 return;
182 }
183
184 auto* window = GTK_WINDOW(topLevel);
185 if (gtk_window_is_maximized(window)) {
186 completionHandler();
187 return;
188 }
189
190 g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Maximize, WTFMove(completionHandler)), [](gpointer userData) {
191 delete static_cast<WindowStateEvent*>(userData);
192 });
193 g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
194 gtk_window_maximize(window);
195#if ENABLE(DEVELOPER_MODE)
196 // Xvfb doesn't support maximize, so we resize the window to the screen size.
197 if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11) {
198 const char* underXvfb = g_getenv("UNDER_XVFB");
199 if (!g_strcmp0(underXvfb, "yes")) {
200 auto screenRect = WebCore::screenAvailableRect(nullptr);
201 gtk_window_move(window, screenRect.x(), screenRect.y());
202 gtk_window_resize(window, screenRect.width(), screenRect.height());
203 }
204 }
205#endif
206 gtk_widget_show(topLevel);
207}
208
209void webkitWebViewMinimizeWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
210{
211 auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
212 if (!gtk_widget_is_toplevel(topLevel)) {
213 completionHandler();
214 return;
215 }
216
217 auto* window = GTK_WINDOW(topLevel);
218 g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Minimize, WTFMove(completionHandler)), [](gpointer userData) {
219 delete static_cast<WindowStateEvent*>(userData);
220 });
221 g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
222 gtk_window_iconify(window);
223 gtk_widget_hide(topLevel);
224}
225
226void webkitWebViewRestoreWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
227{
228 auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
229 if (!gtk_widget_is_toplevel(topLevel)) {
230 completionHandler();
231 return;
232 }
233
234 auto* window = GTK_WINDOW(topLevel);
235 if (gtk_widget_get_mapped(topLevel) && !gtk_window_is_maximized(window)) {
236 completionHandler();
237 return;
238 }
239
240 g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Restore, WTFMove(completionHandler)), [](gpointer userData) {
241 delete static_cast<WindowStateEvent*>(userData);
242 });
243 g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
244 if (gtk_window_is_maximized(window))
245 gtk_window_unmaximize(window);
246 if (!gtk_widget_get_mapped(topLevel))
247 gtk_window_deiconify(window);
248#if ENABLE(DEVELOPER_MODE)
249 // Xvfb doesn't support maximize, so we resize the window to the default size.
250 if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11) {
251 const char* underXvfb = g_getenv("UNDER_XVFB");
252 if (!g_strcmp0(underXvfb, "yes")) {
253 int x, y;
254 gtk_window_get_default_size(window, &x, &y);
255 gtk_window_resize(window, x, y);
256 }
257 }
258#endif
259 gtk_widget_show(topLevel);
260}
261
262/**
263 * webkit_web_view_new:
264 *
265 * Creates a new #WebKitWebView with the default #WebKitWebContext and
266 * no #WebKitUserContentManager associated with it.
267 * See also webkit_web_view_new_with_context(),
268 * webkit_web_view_new_with_user_content_manager(), and
269 * webkit_web_view_new_with_settings().
270 *
271 * Returns: The newly created #WebKitWebView widget
272 */
273GtkWidget* webkit_web_view_new()
274{
275 return webkit_web_view_new_with_context(webkit_web_context_get_default());
276}
277
278/**
279 * webkit_web_view_new_with_context:
280 * @context: the #WebKitWebContext to be used by the #WebKitWebView
281 *
282 * Creates a new #WebKitWebView with the given #WebKitWebContext and
283 * no #WebKitUserContentManager associated with it.
284 * See also webkit_web_view_new_with_user_content_manager() and
285 * webkit_web_view_new_with_settings().
286 *
287 * Returns: The newly created #WebKitWebView widget
288 */
289GtkWidget* webkit_web_view_new_with_context(WebKitWebContext* context)
290{
291 g_return_val_if_fail(WEBKIT_IS_WEB_CONTEXT(context), 0);
292
293 return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
294 "is-ephemeral", webkit_web_context_is_ephemeral(context),
295 "web-context", context,
296 nullptr));
297}
298
299/**
300 * webkit_web_view_new_with_related_view: (constructor)
301 * @web_view: the related #WebKitWebView
302 *
303 * Creates a new #WebKitWebView sharing the same web process with @web_view.
304 * This method doesn't have any effect when %WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS
305 * process model is used, because a single web process is shared for all the web views in the
306 * same #WebKitWebContext. When using %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES process model,
307 * this method should always be used when creating the #WebKitWebView in the #WebKitWebView::create signal.
308 * You can also use this method to implement other process models based on %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES,
309 * like for example, sharing the same web process for all the views in the same security domain.
310 *
311 * The newly created #WebKitWebView will also have the same #WebKitUserContentManager
312 * and #WebKitSettings as @web_view.
313 *
314 * Returns: (transfer full): The newly created #WebKitWebView widget
315 *
316 * Since: 2.4
317 */
318GtkWidget* webkit_web_view_new_with_related_view(WebKitWebView* webView)
319{
320 g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
321
322 return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
323 "user-content-manager", webkit_web_view_get_user_content_manager(webView),
324 "settings", webkit_web_view_get_settings(webView),
325 "related-view", webView,
326 nullptr));
327}
328
329/**
330 * webkit_web_view_new_with_settings:
331 * @settings: a #WebKitSettings
332 *
333 * Creates a new #WebKitWebView with the given #WebKitSettings.
334 * See also webkit_web_view_new_with_context(), and
335 * webkit_web_view_new_with_user_content_manager().
336 *
337 * Returns: The newly created #WebKitWebView widget
338 *
339 * Since: 2.6
340 */
341GtkWidget* webkit_web_view_new_with_settings(WebKitSettings* settings)
342{
343 g_return_val_if_fail(WEBKIT_IS_SETTINGS(settings), nullptr);
344 return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, nullptr));
345}
346
347/**
348 * webkit_web_view_new_with_user_content_manager:
349 * @user_content_manager: a #WebKitUserContentManager.
350 *
351 * Creates a new #WebKitWebView with the given #WebKitUserContentManager.
352 * The content loaded in the view may be affected by the content injected
353 * in the view by the user content manager.
354 *
355 * Returns: The newly created #WebKitWebView widget
356 *
357 * Since: 2.6
358 */
359GtkWidget* webkit_web_view_new_with_user_content_manager(WebKitUserContentManager* userContentManager)
360{
361 g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(userContentManager), nullptr);
362
363 return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, "user-content-manager", userContentManager, nullptr));
364}
365
366/**
367 * webkit_web_view_set_background_color:
368 * @web_view: a #WebKitWebView
369 * @rgba: a #GdkRGBA
370 *
371 * Sets the color that will be used to draw the @web_view background before
372 * the actual contents are rendered. Note that if the web page loaded in @web_view
373 * specifies a background color, it will take precedence over the @rgba color.
374 * By default the @web_view background color is opaque white.
375 * Note that the parent window must have a RGBA visual and
376 * #GtkWidget:app-paintable property set to %TRUE for backgrounds colors to work.
377 *
378 * <informalexample><programlisting>
379 * static void browser_window_set_background_color (BrowserWindow *window,
380 * const GdkRGBA *rgba)
381 * {
382 * WebKitWebView *web_view;
383 * GdkScreen *screen = gtk_window_get_screen (GTK_WINDOW (window));
384 * GdkVisual *rgba_visual = gdk_screen_get_rgba_visual (screen);
385 *
386 * if (!rgba_visual)
387 * return;
388 *
389 * gtk_widget_set_visual (GTK_WIDGET (window), rgba_visual);
390 * gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
391 *
392 * web_view = browser_window_get_web_view (window);
393 * webkit_web_view_set_background_color (web_view, rgba);
394 * }
395 * </programlisting></informalexample>
396 *
397 * Since: 2.8
398 */
399void webkit_web_view_set_background_color(WebKitWebView* webView, const GdkRGBA* rgba)
400{
401 g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
402 g_return_if_fail(rgba);
403
404 auto& page = *webkitWebViewBaseGetPage(reinterpret_cast<WebKitWebViewBase*>(webView));
405 page.setBackgroundColor(WebCore::Color(*rgba));
406}
407
408/**
409 * webkit_web_view_get_background_color:
410 * @web_view: a #WebKitWebView
411 * @rgba: (out): a #GdkRGBA to fill in with the background color
412 *
413 * Gets the color that is used to draw the @web_view background before
414 * the actual contents are rendered.
415 * For more information see also webkit_web_view_set_background_color()
416 *
417 * Since: 2.8
418 */
419void webkit_web_view_get_background_color(WebKitWebView* webView, GdkRGBA* rgba)
420{
421 g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
422 g_return_if_fail(rgba);
423
424 auto& page = *webkitWebViewBaseGetPage(reinterpret_cast<WebKitWebViewBase*>(webView));
425 *rgba = page.backgroundColor().valueOr(WebCore::Color::white);
426}
427