Unreviewed, rolling out r241620.
[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/GtkUtilities.h>
28 #include <WebCore/PlatformDisplay.h>
29 #include <WebCore/PlatformScreen.h>
30 #include <glib/gi18n-lib.h>
31 #include <gtk/gtk.h>
32
33 gboolean webkitWebViewAuthenticate(WebKitWebView* webView, WebKitAuthenticationRequest* request)
34 {
35     CredentialStorageMode credentialStorageMode = webkit_authentication_request_can_save_credentials(request) ? AllowPersistentStorage : DisallowPersistentStorage;
36     webkitWebViewBaseAddDialog(WEBKIT_WEB_VIEW_BASE(webView), webkitAuthenticationDialogNew(request, credentialStorageMode));
37
38     return TRUE;
39 }
40
41 gboolean webkitWebViewScriptDialog(WebKitWebView* webView, WebKitScriptDialog* scriptDialog)
42 {
43     GUniquePtr<char> title(g_strdup_printf("JavaScript - %s", webkitWebViewGetPage(webView).pageLoadState().url().utf8().data()));
44     // Limit script dialog size to 80% of the web view size.
45     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) };
46     webkitWebViewBaseAddDialog(WEBKIT_WEB_VIEW_BASE(webView), webkitScriptDialogImplNew(scriptDialog, title.get(), &maxSize));
47
48     return TRUE;
49 }
50
51 static void fileChooserDialogResponseCallback(GtkFileChooser* dialog, gint responseID, WebKitFileChooserRequest* request)
52 {
53     GRefPtr<WebKitFileChooserRequest> adoptedRequest = adoptGRef(request);
54     if (responseID == GTK_RESPONSE_ACCEPT) {
55         GUniquePtr<GSList> filesList(gtk_file_chooser_get_filenames(dialog));
56         GRefPtr<GPtrArray> filesArray = adoptGRef(g_ptr_array_new());
57         for (GSList* file = filesList.get(); file; file = g_slist_next(file))
58             g_ptr_array_add(filesArray.get(), file->data);
59         g_ptr_array_add(filesArray.get(), 0);
60         webkit_file_chooser_request_select_files(adoptedRequest.get(), reinterpret_cast<const gchar* const*>(filesArray->pdata));
61     } else
62         webkit_file_chooser_request_cancel(adoptedRequest.get());
63
64 #if GTK_CHECK_VERSION(3, 20, 0)
65     g_object_unref(dialog);
66 #else
67     gtk_widget_destroy(GTK_WIDGET(dialog));
68 #endif
69 }
70
71 gboolean webkitWebViewRunFileChooser(WebKitWebView* webView, WebKitFileChooserRequest* request)
72 {
73     GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(webView));
74     if (!WebCore::widgetIsOnscreenToplevelWindow(toplevel))
75         toplevel = 0;
76
77     gboolean allowsMultipleSelection = webkit_file_chooser_request_get_select_multiple(request);
78
79 #if GTK_CHECK_VERSION(3, 20, 0)
80     GtkFileChooserNative* dialog = gtk_file_chooser_native_new(allowsMultipleSelection ? _("Select Files") : _("Select File"),
81         toplevel ? GTK_WINDOW(toplevel) : nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr);
82     if (toplevel)
83         gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(dialog), TRUE);
84 #else
85     GtkWidget* dialog = gtk_file_chooser_dialog_new(allowsMultipleSelection ? _("Select Files") : _("Select File"),
86         toplevel ? GTK_WINDOW(toplevel) : nullptr,
87         GTK_FILE_CHOOSER_ACTION_OPEN,
88         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
89         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
90         nullptr);
91     if (toplevel)
92         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
93 #endif
94
95     if (GtkFileFilter* filter = webkit_file_chooser_request_get_mime_types_filter(request))
96         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
97     gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), allowsMultipleSelection);
98
99     if (const gchar* const* selectedFiles = webkit_file_chooser_request_get_selected_files(request))
100         gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(dialog), selectedFiles[0]);
101
102     g_signal_connect(dialog, "response", G_CALLBACK(fileChooserDialogResponseCallback), g_object_ref(request));
103
104 #if GTK_CHECK_VERSION(3, 20, 0)
105     gtk_native_dialog_show(GTK_NATIVE_DIALOG(dialog));
106 #else
107     gtk_widget_show(dialog);
108 #endif
109
110     return TRUE;
111 }
112
113 struct WindowStateEvent {
114     enum class Type { Maximize, Minimize, Restore };
115
116     WindowStateEvent(Type type, CompletionHandler<void()>&& completionHandler)
117         : type(type)
118         , completionHandler(WTFMove(completionHandler))
119         , completeTimer(RunLoop::main(), this, &WindowStateEvent::complete)
120     {
121         // Complete the event if not done after one second.
122         completeTimer.startOneShot(1_s);
123     }
124
125     ~WindowStateEvent()
126     {
127         complete();
128     }
129
130     void complete()
131     {
132         if (auto handler = std::exchange(completionHandler, nullptr))
133             handler();
134     }
135
136     Type type;
137     CompletionHandler<void()> completionHandler;
138     RunLoop::Timer<WindowStateEvent> completeTimer;
139 };
140
141 static const char* gWindowStateEventID = "wk-window-state-event";
142
143 static gboolean windowStateEventCallback(GtkWidget* window, GdkEventWindowState* event, WebKitWebView* view)
144 {
145     auto* state = static_cast<WindowStateEvent*>(g_object_get_data(G_OBJECT(view), gWindowStateEventID));
146     if (!state) {
147         g_signal_handlers_disconnect_by_func(window, reinterpret_cast<gpointer>(windowStateEventCallback), view);
148         return FALSE;
149     }
150
151     bool eventCompleted = false;
152     switch (state->type) {
153     case WindowStateEvent::Type::Maximize:
154         if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED)
155             eventCompleted = true;
156         break;
157     case WindowStateEvent::Type::Minimize:
158         if ((event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) || !gtk_widget_get_mapped(window))
159             eventCompleted = true;
160         break;
161     case WindowStateEvent::Type::Restore:
162         if (!(event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) && !(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED))
163             eventCompleted = true;
164         break;
165     }
166
167     if (eventCompleted) {
168         g_signal_handlers_disconnect_by_func(window, reinterpret_cast<gpointer>(windowStateEventCallback), view);
169         g_object_set_data(G_OBJECT(view), gWindowStateEventID, nullptr);
170     }
171
172     return FALSE;
173 }
174
175 void webkitWebViewMaximizeWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
176 {
177     auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
178     if (!gtk_widget_is_toplevel(topLevel)) {
179         completionHandler();
180         return;
181     }
182
183     auto* window = GTK_WINDOW(topLevel);
184     if (gtk_window_is_maximized(window)) {
185         completionHandler();
186         return;
187     }
188
189     g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Maximize, WTFMove(completionHandler)), [](gpointer userData) {
190         delete static_cast<WindowStateEvent*>(userData);
191     });
192     g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
193     gtk_window_maximize(window);
194 #if ENABLE(DEVELOPER_MODE)
195     // Xvfb doesn't support maximize, so we resize the window to the screen size.
196     if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11) {
197         const char* underXvfb = g_getenv("UNDER_XVFB");
198         if (!g_strcmp0(underXvfb, "yes")) {
199             auto screenRect = WebCore::screenAvailableRect(nullptr);
200             gtk_window_move(window, screenRect.x(), screenRect.y());
201             gtk_window_resize(window, screenRect.width(), screenRect.height());
202         }
203     }
204 #endif
205     gtk_widget_show(topLevel);
206 }
207
208 void webkitWebViewMinimizeWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
209 {
210     auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
211     if (!gtk_widget_is_toplevel(topLevel)) {
212         completionHandler();
213         return;
214     }
215
216     auto* window = GTK_WINDOW(topLevel);
217     g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Minimize, WTFMove(completionHandler)), [](gpointer userData) {
218         delete static_cast<WindowStateEvent*>(userData);
219     });
220     g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
221     gtk_window_iconify(window);
222     gtk_widget_hide(topLevel);
223 }
224
225 void webkitWebViewRestoreWindow(WebKitWebView* view, CompletionHandler<void()>&& completionHandler)
226 {
227     auto* topLevel = gtk_widget_get_toplevel(GTK_WIDGET(view));
228     if (!gtk_widget_is_toplevel(topLevel)) {
229         completionHandler();
230         return;
231     }
232
233     auto* window = GTK_WINDOW(topLevel);
234     if (gtk_widget_get_mapped(topLevel) && !gtk_window_is_maximized(window)) {
235         completionHandler();
236         return;
237     }
238
239     g_object_set_data_full(G_OBJECT(view), gWindowStateEventID, new WindowStateEvent(WindowStateEvent::Type::Restore, WTFMove(completionHandler)), [](gpointer userData) {
240         delete static_cast<WindowStateEvent*>(userData);
241     });
242     g_signal_connect_object(window, "window-state-event", G_CALLBACK(windowStateEventCallback), view, G_CONNECT_AFTER);
243     if (gtk_window_is_maximized(window))
244         gtk_window_unmaximize(window);
245     if (!gtk_widget_get_mapped(topLevel))
246         gtk_window_deiconify(window);
247 #if ENABLE(DEVELOPER_MODE)
248     // Xvfb doesn't support maximize, so we resize the window to the default size.
249     if (WebCore::PlatformDisplay::sharedDisplay().type() == WebCore::PlatformDisplay::Type::X11) {
250         const char* underXvfb = g_getenv("UNDER_XVFB");
251         if (!g_strcmp0(underXvfb, "yes")) {
252             int x, y;
253             gtk_window_get_default_size(window, &x, &y);
254             gtk_window_resize(window, x, y);
255         }
256     }
257 #endif
258     gtk_widget_show(topLevel);
259 }
260
261 /**
262  * webkit_web_view_new:
263  *
264  * Creates a new #WebKitWebView with the default #WebKitWebContext and
265  * no #WebKitUserContentManager associated with it.
266  * See also webkit_web_view_new_with_context(),
267  * webkit_web_view_new_with_user_content_manager(), and
268  * webkit_web_view_new_with_settings().
269  *
270  * Returns: The newly created #WebKitWebView widget
271  */
272 GtkWidget* webkit_web_view_new()
273 {
274     return webkit_web_view_new_with_context(webkit_web_context_get_default());
275 }
276
277 /**
278  * webkit_web_view_new_with_context:
279  * @context: the #WebKitWebContext to be used by the #WebKitWebView
280  *
281  * Creates a new #WebKitWebView with the given #WebKitWebContext and
282  * no #WebKitUserContentManager associated with it.
283  * See also webkit_web_view_new_with_user_content_manager() and
284  * webkit_web_view_new_with_settings().
285  *
286  * Returns: The newly created #WebKitWebView widget
287  */
288 GtkWidget* webkit_web_view_new_with_context(WebKitWebContext* context)
289 {
290     g_return_val_if_fail(WEBKIT_IS_WEB_CONTEXT(context), 0);
291
292     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
293         "is-ephemeral", webkit_web_context_is_ephemeral(context),
294         "web-context", context,
295         nullptr));
296 }
297
298 /**
299  * webkit_web_view_new_with_related_view: (constructor)
300  * @web_view: the related #WebKitWebView
301  *
302  * Creates a new #WebKitWebView sharing the same web process with @web_view.
303  * This method doesn't have any effect when %WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS
304  * process model is used, because a single web process is shared for all the web views in the
305  * same #WebKitWebContext. When using %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES process model,
306  * this method should always be used when creating the #WebKitWebView in the #WebKitWebView::create signal.
307  * You can also use this method to implement other process models based on %WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES,
308  * like for example, sharing the same web process for all the views in the same security domain.
309  *
310  * The newly created #WebKitWebView will also have the same #WebKitUserContentManager
311  * and #WebKitSettings as @web_view.
312  *
313  * Returns: (transfer full): The newly created #WebKitWebView widget
314  *
315  * Since: 2.4
316  */
317 GtkWidget* webkit_web_view_new_with_related_view(WebKitWebView* webView)
318 {
319     g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), nullptr);
320
321     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
322         "user-content-manager", webkit_web_view_get_user_content_manager(webView),
323         "settings", webkit_web_view_get_settings(webView),
324         "related-view", webView,
325         nullptr));
326 }
327
328 /**
329  * webkit_web_view_new_with_settings:
330  * @settings: a #WebKitSettings
331  *
332  * Creates a new #WebKitWebView with the given #WebKitSettings.
333  * See also webkit_web_view_new_with_context(), and
334  * webkit_web_view_new_with_user_content_manager().
335  *
336  * Returns: The newly created #WebKitWebView widget
337  *
338  * Since: 2.6
339  */
340 GtkWidget* webkit_web_view_new_with_settings(WebKitSettings* settings)
341 {
342     g_return_val_if_fail(WEBKIT_IS_SETTINGS(settings), nullptr);
343     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, "settings", settings, nullptr));
344 }
345
346 /**
347  * webkit_web_view_new_with_user_content_manager:
348  * @user_content_manager: a #WebKitUserContentManager.
349  *
350  * Creates a new #WebKitWebView with the given #WebKitUserContentManager.
351  * The content loaded in the view may be affected by the content injected
352  * in the view by the user content manager.
353  *
354  * Returns: The newly created #WebKitWebView widget
355  *
356  * Since: 2.6
357  */
358 GtkWidget* webkit_web_view_new_with_user_content_manager(WebKitUserContentManager* userContentManager)
359 {
360     g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_MANAGER(userContentManager), nullptr);
361
362     return GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW, "user-content-manager", userContentManager, nullptr));
363 }