| 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 | #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 | |
| 72 | gboolean 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 | |
| 114 | struct 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 | |
| 142 | static const char* gWindowStateEventID = "wk-window-state-event" ; |
| 143 | |
| 144 | static 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 | |
| 176 | void 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 | |
| 209 | void 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 | |
| 226 | void 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 | */ |
| 273 | GtkWidget* 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 | */ |
| 289 | GtkWidget* 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 | */ |
| 318 | GtkWidget* 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 | */ |
| 341 | GtkWidget* 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 | */ |
| 359 | GtkWidget* 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 | */ |
| 399 | void 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 | */ |
| 419 | void 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 | |