[WPE][GTK] Bump minimum versions of GLib, GTK, libsoup, ATK, GStreamer, and Cairo
[WebKit-https.git] / Source / WebKit / UIProcess / API / gtk / WebKitWebViewGtk.cpp
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
34 gboolean 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
42 gboolean 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
52 static 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     g_object_unref(dialog);
66 }
67
68 gboolean webkitWebViewRunFileChooser(WebKitWebView* webView, WebKitFileChooserRequest* request)
69 {
70     GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(webView));
71     if (!WebCore::widgetIsOnscreenToplevelWindow(toplevel))
72         toplevel = 0;
73
74     gboolean allowsMultipleSelection = webkit_file_chooser_request_get_select_multiple(request);
75
76     GtkFileChooserNative* dialog = gtk_file_chooser_native_new(allowsMultipleSelection ? _("Select Files") : _("Select File"),
77         toplevel ? GTK_WINDOW(toplevel) : nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr);
78     if (toplevel)
79         gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(dialog), TRUE);
80
81     if (GtkFileFilter* filter = webkit_file_chooser_request_get_mime_types_filter(request))
82         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
83     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), allowsMultipleSelection);
84
85     if (const gchar* const* selectedFiles = webkit_file_chooser_request_get_selected_files(request))
86         gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(dialog), selectedFiles[0]);
87
88     g_signal_connect(dialog, "response", G_CALLBACK(fileChooserDialogResponseCallback), g_object_ref(request));
89
90     gtk_native_dialog_show(GTK_NATIVE_DIALOG(dialog));
91
92     return TRUE;
93 }
94
95 struct WindowStateEvent {
96     enum class Type { Maximize, Minimize, Restore };
97
98     WindowStateEvent(Type type, CompletionHandler<void()>&& completionHandler)
99         : type(type)
100         , completionHandler(WTFMove(completionHandler))
101         , completeTimer(RunLoop::main(), this, &WindowStateEvent::complete)
102     {
103         // Complete the event if not done after one second.
104         completeTimer.startOneShot(1_s);
105     }
106
107     ~WindowStateEvent()
108     {
109         complete();
110     }
111
112     void complete()
113     {
114         if (auto handler = std::exchange(completionHandler, nullptr))
115             handler();
116     }
117
118     Type type;
119     CompletionHandler<void()> completionHandler;
120     RunLoop::Timer<WindowStateEvent> completeTimer;
121 };
122
123 static const char* gWindowStateEventID = "wk-window-state-event";
124
125 static gboolean windowStateEventCallback(GtkWidget* window, GdkEventWindowState* event, WebKitWebView* view)
126 {
127     auto* state = static_cast<WindowStateEvent*>(g_object_get_data(G_OBJECT(view), gWindowStateEventID));
128     if (!state) {
129         g_signal_handlers_disconnect_by_func(window, reinterpret_cast<gpointer>(windowStateEventCallback), view);
130         return FALSE;
131     }
132
133     bool eventCompleted = false;
134     switch (state->type) {
135     case WindowStateEvent::Type::Maximize:
136         if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
137             eventCompleted = true;
138         break;
139     case WindowStateEvent::Type::Minimize:
140         if ((event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) || !gtk_widget_get_mapped(window))
141             eventCompleted = true;
142         break;
143     case WindowStateEvent::Type::Restore:
144         if (!(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) && !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
145             eventCompleted = true;
146         break;
147     }
148
149     if (eventCompleted) {
150         g_signal_handlers_disconnect_by_func(window, reinterpret_cast<gpointer>(windowStateEventCallback), view);
151         g_object_set_data(G_OBJECT(view), gWindowStateEventID, nullptr);
152     }
153
154     return FALSE;
155 }
156
157 void webkitWebViewMaximizeWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
158 {
159     auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
160     if (!gtk_widget_is_toplevel(topLevel)) {
161         completionHandler();
162         return;
163     }
164
165     auto* window = GTK_WINDOW(topLevel);
166     if (gtk_window_is_maximized(window)) {
167         completionHandler();
168         return;
169     }
170
171     g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Maximize, WTFMove(completionHandler)), [](gpointer userData) {
172         delete static_cast<WindowStateEvent*>(userData);
173     });
174     g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
175     gtk_window_maximize(window);
176 #if ENABLE(DEVELOPER_MODE)
177     // Xvfb doesn't support maximize, so we resize the window to the screen size.
178     if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11) {
179         const char* underXvfb = g_getenv("UNDER_XVFB");
180         if (!g_strcmp0(underXvfb, "yes")) {
181             auto screenRect = WebCore::screenAvailableRect(nullptr);
182             gtk_window_move(window, screenRect.x(), screenRect.y());
183             gtk_window_resize(window, screenRect.width(), screenRect.height());
184         }
185     }
186 #endif
187     gtk_widget_show(topLevel);
188 }
189
190 void webkitWebViewMinimizeWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
191 {
192     auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
193     if (!gtk_widget_is_toplevel(topLevel)) {
194         completionHandler();
195         return;
196     }
197
198     auto* window = GTK_WINDOW(topLevel);
199     g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Minimize, WTFMove(completionHandler)), [](gpointer userData) {
200         delete static_cast<WindowStateEvent*>(userData);
201     });
202     g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
203     gtk_window_iconify(window);
204     gtk_widget_hide(topLevel);
205 }
206
207 void webkitWebViewRestoreWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
208 {
209     auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
210     if (!gtk_widget_is_toplevel(topLevel)) {
211         completionHandler();
212         return;
213     }
214
215     auto* window = GTK_WINDOW(topLevel);
216     if (gtk_widget_get_mapped(topLevel) && !gtk_window_is_maximized(window)) {
217         completionHandler();
218         return;
219     }
220
221     g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Restore, WTFMove(completionHandler)), [](gpointer userData) {
222         delete static_cast<WindowStateEvent*>(userData);
223     });
224     g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
225     if (gtk_window_is_maximized(window))
226         gtk_window_unmaximize(window);
227     if (!gtk_widget_get_mapped(topLevel))
228         gtk_window_deiconify(window);
229 #if ENABLE(DEVELOPER_MODE)
230     // Xvfb doesn't support maximize, so we resize the window to the default size.
231     if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11) {
232         const char* underXvfb = g_getenv("UNDER_XVFB");
233         if (!g_strcmp0(underXvfb, "yes")) {
234             int x, y;
235             gtk_window_get_default_size(window, &x, &y);
236             gtk_window_resize(window, x, y);
237         }
238     }
239 #endif
240     gtk_widget_show(topLevel);
241 }
242
243 /**
244  * webkit_web_view_new:
245  *
246  * Creates a new #WebKitWebView with the default #WebKitWebContext and
247  * no #WebKitUserContentManager associated with it.
248  * See also webkit_web_view_new_with_context(),
249  * webkit_web_view_new_with_user_content_manager(), and
250  * webkit_web_view_new_with_settings().
251  *
252  * Returns: The newly created #WebKitWebView widget
253  */
254 GtkWidget* webkit_web_view_new()
255 {
256     return webkit_web_view_new_with_context(webkit_web_context_get_default());
257 }
258
259 /**
260  * webkit_web_view_new_with_context:
261  * @context: the #WebKitWebContext to be used by the #WebKitWebView
262  *
263  * Creates a new #WebKitWebView with the given #WebKitWebContext and
264  * no #WebKitUserContentManager associated with it.
265  * See also webkit_web_view_new_with_user_content_manager() and
266  * webkit_web_view_new_with_settings().
267  *
268  * Returns: The newly created #WebKitWebView widget
269  */
270 GtkWidget* webkit_web_view_new_with_context(WebKitWebContext* context)
271 {
272     g_return_val_if_fail(WEBKIT_IS_WEB_CONTEXT(context), 0);
273
274     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
275         "is-ephemeral", webkit_web_context_is_ephemeral(context),
276         "web-context", context,
277         nullptr));
278 }
279
280 /**
281  * webkit_web_view_new_with_related_view: (constructor)
282  * @web_view: the related #WebKitWebView
283  *
284  * Creates a new #WebKitWebView sharing the same web process with @web_view.
285  * This method doesn't have any effect when %WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS
286  * process model is used, because a single web process is shared for all the web views in the
287  * same #WebKitWebContext. When using %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES process model,
288  * this method should always be used when creating the #WebKitWebView in the #WebKitWebView::create signal.
289  * You can also use this method to implement other process models based on %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES,
290  * like for example, sharing the same web process for all the views in the same security domain.
291  *
292  * The newly created #WebKitWebView will also have the same #WebKitUserContentManager
293  * and #WebKitSettings as @web_view.
294  *
295  * Returns: (transfer full): The newly created #WebKitWebView widget
296  *
297  * Since: 2.4
298  */
299 GtkWidget* webkit_web_view_new_with_related_view(WebKitWebView* webView)
300 {
301     g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
302
303     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
304         "user-content-manager", webkit_web_view_get_user_content_manager(webView),
305         "settings", webkit_web_view_get_settings(webView),
306         "related-view", webView,
307         nullptr));
308 }
309
310 /**
311  * webkit_web_view_new_with_settings:
312  * @settings: a #WebKitSettings
313  *
314  * Creates a new #WebKitWebView with the given #WebKitSettings.
315  * See also webkit_web_view_new_with_context(), and
316  * webkit_web_view_new_with_user_content_manager().
317  *
318  * Returns: The newly created #WebKitWebView widget
319  *
320  * Since: 2.6
321  */
322 GtkWidget* webkit_web_view_new_with_settings(WebKitSettings* settings)
323 {
324     g_return_val_if_fail(WEBKIT_IS_SETTINGS(settings), nullptr);
325     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, nullptr));
326 }
327
328 /**
329  * webkit_web_view_new_with_user_content_manager:
330  * @user_content_manager: a #WebKitUserContentManager.
331  *
332  * Creates a new #WebKitWebView with the given #WebKitUserContentManager.
333  * The content loaded in the view may be affected by the content injected
334  * in the view by the user content manager.
335  *
336  * Returns: The newly created #WebKitWebView widget
337  *
338  * Since: 2.6
339  */
340 GtkWidget* webkit_web_view_new_with_user_content_manager(WebKitUserContentManager* userContentManager)
341 {
342     g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(userContentManager), nullptr);
343
344     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, "user-content-manager", userContentManager, nullptr));
345 }
346
347 /**
348  * webkit_web_view_set_background_color:
349  * @web_view: a #WebKitWebView
350  * @rgba: a #GdkRGBA
351  *
352  * Sets the color that will be used to draw the @web_view background before
353  * the actual contents are rendered. Note that if the web page loaded in @web_view
354  * specifies a background color, it will take precedence over the @rgba color.
355  * By default the @web_view background color is opaque white.
356  * Note that the parent window must have a RGBA visual and
357  * #GtkWidget:app-paintable property set to %TRUE for backgrounds colors to work.
358  *
359  * <informalexample><programlisting>
360  * static void browser_window_set_background_color (BrowserWindow *window,
361  *                                                  const GdkRGBA *rgba)
362  * {
363  *     WebKitWebView *web_view;
364  *     GdkScreen *screen = gtk_window_get_screen (GTK_WINDOW (window));
365  *     GdkVisual *rgba_visual = gdk_screen_get_rgba_visual (screen);
366  *
367  *     if (!rgba_visual)
368  *          return;
369  *
370  *     gtk_widget_set_visual (GTK_WIDGET (window), rgba_visual);
371  *     gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
372  *
373  *     web_view = browser_window_get_web_view (window);
374  *     webkit_web_view_set_background_color (web_view, rgba);
375  * }
376  * </programlisting></informalexample>
377  *
378  * Since: 2.8
379  */
380 void webkit_web_view_set_background_color(WebKitWebView* webView, const GdkRGBA* rgba)
381 {
382     g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
383     g_return_if_fail(rgba);
384
385     auto& page = *webkitWebViewBaseGetPage(reinterpret_cast<WebKitWebViewBase*>(webView));
386     page.setBackgroundColor(WebCore::Color(*rgba));
387 }
388
389 /**
390  * webkit_web_view_get_background_color:
391  * @web_view: a #WebKitWebView
392  * @rgba: (out): a #GdkRGBA to fill in with the background color
393  *
394  * Gets the color that is used to draw the @web_view background before
395  * the actual contents are rendered.
396  * For more information see also webkit_web_view_set_background_color()
397  *
398  * Since: 2.8
399  */
400 void webkit_web_view_get_background_color(WebKitWebView* webView, GdkRGBA* rgba)
401 {
402     g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
403     g_return_if_fail(rgba);
404
405     auto& page = *webkitWebViewBaseGetPage(reinterpret_cast<WebKitWebViewBase*>(webView));
406     *rgba = page.backgroundColor().valueOr(WebCore::Color::white);
407 }