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