[GTK] Add support to load/save session in MiniBrowser
[WebKit-https.git] / Tools / MiniBrowser / gtk / BrowserWindow.c
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc.
3  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4  * Copyright (C) 2011 Igalia S.L.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25  * THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H && defined(BUILDING_WITH_CMAKE)
29 #include "cmakeconfig.h"
30 #endif
31 #include "BrowserWindow.h"
32
33 #include "BrowserDownloadsBar.h"
34 #include "BrowserSearchBar.h"
35 #include "BrowserSettingsDialog.h"
36 #include <gdk/gdkkeysyms.h>
37 #include <string.h>
38
39 enum {
40     PROP_0,
41
42     PROP_VIEW
43 };
44
45 struct _BrowserWindow {
46     GtkWindow parent;
47
48     GtkAccelGroup *accelGroup;
49     GtkWidget *mainBox;
50     GtkWidget *toolbar;
51     GtkWidget *uriEntry;
52     GtkWidget *backItem;
53     GtkWidget *forwardItem;
54     GtkWidget *zoomInItem;
55     GtkWidget *zoomOutItem;
56     GtkWidget *boldItem;
57     GtkWidget *italicItem;
58     GtkWidget *underlineItem;
59     GtkWidget *strikethroughItem;
60     GtkWidget *statusLabel;
61     GtkWidget *settingsDialog;
62     WebKitWebView *webView;
63     GtkWidget *downloadsBar;
64     BrowserSearchBar *searchBar;
65     gboolean searchBarVisible;
66     gboolean inspectorWindowIsVisible;
67     gboolean fullScreenIsEnabled;
68     GdkPixbuf *favicon;
69     GtkWidget *reloadOrStopButton;
70     GtkWidget *fullScreenMessageLabel;
71     GtkWindow *parentWindow;
72     guint fullScreenMessageLabelId;
73     guint resetEntryProgressTimeoutId;
74     gchar *sessionFile;
75 };
76
77 struct _BrowserWindowClass {
78     GtkWindowClass parent;
79 };
80
81 static const char *defaultWindowTitle = "WebKitGTK+ MiniBrowser";
82 static const char *miniBrowserAboutScheme = "minibrowser-about";
83 static const gdouble minimumZoomLevel = 0.5;
84 static const gdouble maximumZoomLevel = 3;
85 static const gdouble defaultZoomLevel = 1;
86 static const gdouble zoomStep = 1.2;
87 static gint windowCount = 0;
88
89 G_DEFINE_TYPE(BrowserWindow, browser_window, GTK_TYPE_WINDOW)
90
91 static char *getInternalURI(const char *uri)
92 {
93     // Internally we use minibrowser-about: as about: prefix is ignored by WebKit.
94     if (g_str_has_prefix(uri, "about:") && !g_str_equal(uri, "about:blank"))
95         return g_strconcat(miniBrowserAboutScheme, uri + strlen ("about"), NULL);
96
97     return g_strdup(uri);
98 }
99
100 static char *getExternalURI(const char *uri)
101 {
102     // From the user point of view we support about: prefix.
103     if (g_str_has_prefix(uri, miniBrowserAboutScheme))
104         return g_strconcat("about", uri + strlen(miniBrowserAboutScheme), NULL);
105
106     return g_strdup(uri);
107 }
108
109 static void browserWindowSetStatusText(BrowserWindow *window, const char *text)
110 {
111     gtk_label_set_text(GTK_LABEL(window->statusLabel), text);
112     gtk_widget_set_visible(window->statusLabel, !!text);
113 }
114
115 static void resetStatusText(GtkWidget *widget, BrowserWindow *window)
116 {
117     browserWindowSetStatusText(window, NULL);
118 }
119
120 static void activateUriEntryCallback(BrowserWindow *window)
121 {
122     browser_window_load_uri(window, gtk_entry_get_text(GTK_ENTRY(window->uriEntry)));
123 }
124
125 static void reloadOrStopCallback(BrowserWindow *window)
126 {
127     if (webkit_web_view_is_loading(window->webView))
128         webkit_web_view_stop_loading(window->webView);
129     else
130         webkit_web_view_reload(window->webView);
131 }
132
133 static void goBackCallback(BrowserWindow *window)
134 {
135     webkit_web_view_go_back(window->webView);
136 }
137
138 static void goForwardCallback(BrowserWindow *window)
139 {
140     webkit_web_view_go_forward(window->webView);
141 }
142
143 static void settingsCallback(BrowserWindow *window)
144 {
145     if (window->settingsDialog) {
146         gtk_window_present(GTK_WINDOW(window->settingsDialog));
147         return;
148     }
149
150     window->settingsDialog = browser_settings_dialog_new(webkit_web_view_get_settings(window->webView));
151     gtk_window_set_transient_for(GTK_WINDOW(window->settingsDialog), GTK_WINDOW(window));
152     g_object_add_weak_pointer(G_OBJECT(window->settingsDialog), (gpointer *)&window->settingsDialog);
153     gtk_widget_show(window->settingsDialog);
154 }
155
156 static void webViewURIChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
157 {
158     char *externalURI = getExternalURI(webkit_web_view_get_uri(webView));
159     gtk_entry_set_text(GTK_ENTRY(window->uriEntry), externalURI);
160     g_free(externalURI);
161 }
162
163 static void webViewTitleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
164 {
165     const char *title = webkit_web_view_get_title(webView);
166     gtk_window_set_title(GTK_WINDOW(window), title ? title : defaultWindowTitle);
167 }
168
169 static gboolean resetEntryProgress(BrowserWindow *window)
170 {
171     gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), 0);
172     window->resetEntryProgressTimeoutId = 0;
173     return FALSE;
174 }
175
176 static void webViewLoadProgressChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
177 {
178     gdouble progress = webkit_web_view_get_estimated_load_progress(webView);
179     gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), progress);
180     if (progress == 1.0) {
181         window->resetEntryProgressTimeoutId = g_timeout_add(500, (GSourceFunc)resetEntryProgress, window);
182         g_source_set_name_by_id(window->resetEntryProgressTimeoutId, "[WebKit] resetEntryProgress");
183     }
184 }
185
186 static void downloadStarted(WebKitWebContext *webContext, WebKitDownload *download, BrowserWindow *window)
187 {
188     if (!window->downloadsBar) {
189         window->downloadsBar = browser_downloads_bar_new();
190         gtk_box_pack_start(GTK_BOX(window->mainBox), window->downloadsBar, FALSE, FALSE, 0);
191         gtk_box_reorder_child(GTK_BOX(window->mainBox), window->downloadsBar, 0);
192         g_object_add_weak_pointer(G_OBJECT(window->downloadsBar), (gpointer *)&(window->downloadsBar));
193         gtk_widget_show(window->downloadsBar);
194     }
195     browser_downloads_bar_add_download(BROWSER_DOWNLOADS_BAR(window->downloadsBar), download);
196 }
197
198 static void browserWindowHistoryItemSelected(BrowserWindow *window, GtkMenuItem *item)
199 {
200     GtkAction *action = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item));
201     browserWindowSetStatusText(window, action ? gtk_action_get_name(action) : NULL);
202 }
203
204 static void browserWindowHistoryItemActivated(BrowserWindow *window, GtkAction *action)
205 {
206     WebKitBackForwardListItem *item = g_object_get_data(G_OBJECT(action), "back-forward-list-item");
207     if (!item)
208         return;
209
210     webkit_web_view_go_to_back_forward_list_item(window->webView, item);
211 }
212
213 static GtkWidget *browserWindowCreateBackForwardMenu(BrowserWindow *window, GList *list)
214 {
215     if (!list)
216         return NULL;
217
218     GtkWidget *menu = gtk_menu_new();
219     GList *listItem;
220     for (listItem = list; listItem; listItem = g_list_next(listItem)) {
221         WebKitBackForwardListItem *item = (WebKitBackForwardListItem *)listItem->data;
222         const char *uri = webkit_back_forward_list_item_get_uri(item);
223         const char *title = webkit_back_forward_list_item_get_title(item);
224
225         GtkAction *action = gtk_action_new(uri, title, NULL, NULL);
226         g_object_set_data_full(G_OBJECT(action), "back-forward-list-item", g_object_ref(item), g_object_unref);
227         g_signal_connect_swapped(action, "activate", G_CALLBACK(browserWindowHistoryItemActivated), window);
228
229         GtkWidget *menuItem = gtk_action_create_menu_item(action);
230         g_signal_connect_swapped(menuItem, "select", G_CALLBACK(browserWindowHistoryItemSelected), window);
231         g_object_unref(action);
232
233         gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuItem);
234         gtk_widget_show(menuItem);
235     }
236
237     g_signal_connect(menu, "hide", G_CALLBACK(resetStatusText), window);
238
239     return menu;
240 }
241
242 static void browserWindowUpdateNavigationActions(BrowserWindow *window, WebKitBackForwardList *backForwadlist)
243 {
244     gtk_widget_set_sensitive(window->backItem, webkit_web_view_can_go_back(window->webView));
245     gtk_widget_set_sensitive(window->forwardItem, webkit_web_view_can_go_forward(window->webView));
246
247     GList *list = g_list_reverse(webkit_back_forward_list_get_back_list_with_limit(backForwadlist, 10));
248     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->backItem),
249         browserWindowCreateBackForwardMenu(window, list));
250     g_list_free(list);
251
252     list = webkit_back_forward_list_get_forward_list_with_limit(backForwadlist, 10);
253     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->forwardItem),
254         browserWindowCreateBackForwardMenu(window, list));
255     g_list_free(list);
256 }
257
258 static void backForwadlistChanged(WebKitBackForwardList *backForwadlist, WebKitBackForwardListItem *itemAdded, GList *itemsRemoved, BrowserWindow *window)
259 {
260     browserWindowUpdateNavigationActions(window, backForwadlist);
261 }
262
263 static void permissionRequestDialogCallback(GtkDialog *dialog, gint response, WebKitPermissionRequest *request)
264 {
265     switch (response) {
266     case GTK_RESPONSE_YES:
267         webkit_permission_request_allow(request);
268         break;
269     default:
270         webkit_permission_request_deny(request);
271         break;
272     }
273
274     gtk_widget_destroy(GTK_WIDGET(dialog));
275     g_object_unref(request);
276 }
277
278 static void webViewClose(WebKitWebView *webView, BrowserWindow *window)
279 {
280     gtk_widget_destroy(GTK_WIDGET(window));
281 }
282
283 static void webViewRunAsModal(WebKitWebView *webView, BrowserWindow *window)
284 {
285     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
286     gtk_window_set_transient_for(GTK_WINDOW(window), window->parentWindow);
287 }
288
289 static void webViewReadyToShow(WebKitWebView *webView, BrowserWindow *window)
290 {
291     WebKitWindowProperties *windowProperties = webkit_web_view_get_window_properties(webView);
292
293     GdkRectangle geometry;
294     webkit_window_properties_get_geometry(windowProperties, &geometry);
295     if (geometry.x >= 0 && geometry.y >= 0)
296         gtk_window_move(GTK_WINDOW(window), geometry.x, geometry.y);
297     if (geometry.width > 0 && geometry.height > 0)
298         gtk_window_resize(GTK_WINDOW(window), geometry.width, geometry.height);
299
300     if (!webkit_window_properties_get_toolbar_visible(windowProperties))
301         gtk_widget_hide(window->toolbar);
302     else if (!webkit_window_properties_get_locationbar_visible(windowProperties))
303         gtk_widget_hide(window->uriEntry);
304
305     if (!webkit_window_properties_get_resizable(windowProperties))
306         gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
307
308     gtk_widget_show(GTK_WIDGET(window));
309 }
310
311 static gboolean fullScreenMessageTimeoutCallback(BrowserWindow *window)
312 {
313     gtk_widget_hide(window->fullScreenMessageLabel);
314     window->fullScreenMessageLabelId = 0;
315     return FALSE;
316 }
317
318 static gboolean webViewEnterFullScreen(WebKitWebView *webView, BrowserWindow *window)
319 {
320     gchar *titleOrURI = g_strdup(webkit_web_view_get_title(window->webView));
321     if (!titleOrURI)
322         titleOrURI = getExternalURI(webkit_web_view_get_uri(window->webView));
323     gchar *message = g_strdup_printf("%s is now full screen. Press ESC or f to exit.", titleOrURI);
324     gtk_label_set_text(GTK_LABEL(window->fullScreenMessageLabel), message);
325     g_free(titleOrURI);
326     g_free(message);
327
328     gtk_widget_show(window->fullScreenMessageLabel);
329
330     window->fullScreenMessageLabelId = g_timeout_add_seconds(2, (GSourceFunc)fullScreenMessageTimeoutCallback, window);
331     g_source_set_name_by_id(window->fullScreenMessageLabelId, "[WebKit] fullScreenMessageTimeoutCallback");
332     gtk_widget_hide(window->toolbar);
333     window->searchBarVisible = gtk_widget_get_visible(GTK_WIDGET(window->searchBar));
334     browser_search_bar_close(window->searchBar);
335
336     return FALSE;
337 }
338
339 static gboolean webViewLeaveFullScreen(WebKitWebView *webView, BrowserWindow *window)
340 {
341     if (window->fullScreenMessageLabelId) {
342         g_source_remove(window->fullScreenMessageLabelId);
343         window->fullScreenMessageLabelId = 0;
344     }
345     gtk_widget_hide(window->fullScreenMessageLabel);
346     gtk_widget_show(window->toolbar);
347     if (window->searchBarVisible) {
348         // Opening the search bar steals the focus. Usually, we want
349         // this but not when coming back from fullscreen.
350         GtkWidget *focusWidget = gtk_window_get_focus(GTK_WINDOW(window));
351         browser_search_bar_open(window->searchBar);
352         gtk_window_set_focus(GTK_WINDOW(window), focusWidget);
353     }
354
355     return FALSE;
356 }
357
358 static GtkWidget *webViewCreate(WebKitWebView *webView, WebKitNavigationAction *navigation, BrowserWindow *window)
359 {
360     WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webView));
361     webkit_web_view_set_settings(newWebView, webkit_web_view_get_settings(webView));
362
363     GtkWidget *newWindow = browser_window_new(newWebView, GTK_WINDOW(window));
364     g_signal_connect(newWebView, "ready-to-show", G_CALLBACK(webViewReadyToShow), newWindow);
365     g_signal_connect(newWebView, "run-as-modal", G_CALLBACK(webViewRunAsModal), newWindow);
366     g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), newWindow);
367     return GTK_WIDGET(newWebView);
368 }
369
370 static gboolean webViewLoadFailed(WebKitWebView *webView, WebKitLoadEvent loadEvent, const char *failingURI, GError *error, BrowserWindow *window)
371 {
372     gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), 0.);
373     return FALSE;
374 }
375
376 static gboolean webViewLoadFailedWithTLSerrors(WebKitWebView *webView, const char *failingURI, GTlsCertificate *certificate, GTlsCertificateFlags errors, BrowserWindow *window)
377 {
378     GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
379         GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", "Invalid TLS Certificate");
380     gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "Failed to load %s: Do you want to continue ignoring the TLS errors?", failingURI);
381     int response = gtk_dialog_run(GTK_DIALOG(dialog));
382     gtk_widget_destroy(dialog);
383
384     if (response == GTK_RESPONSE_YES) {
385         SoupURI *uri = soup_uri_new(failingURI);
386         webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(webView), certificate, uri->host);
387         soup_uri_free(uri);
388         webkit_web_view_load_uri(webView, failingURI);
389     }
390
391     return TRUE;
392 }
393
394 static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserWindow *window)
395 {
396     switch (decisionType) {
397     case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: {
398         WebKitNavigationAction *navigationAction = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
399         if (webkit_navigation_action_get_navigation_type(navigationAction) != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
400             || webkit_navigation_action_get_mouse_button(navigationAction) != GDK_BUTTON_MIDDLE)
401             return FALSE;
402
403         // Opening a new window if link clicked with the middle button.
404         WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(webkit_web_view_get_context(webView)));
405         GtkWidget *newWindow = browser_window_new(newWebView, GTK_WINDOW(window));
406         webkit_web_view_load_request(newWebView, webkit_navigation_action_get_request(navigationAction));
407         gtk_widget_show(newWindow);
408
409         webkit_policy_decision_ignore(decision);
410         return TRUE;
411     }
412     case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: {
413         WebKitResponsePolicyDecision *responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision);
414         if (webkit_response_policy_decision_is_mime_type_supported(responseDecision))
415             return FALSE;
416
417         WebKitWebResource *mainResource = webkit_web_view_get_main_resource(webView);
418         WebKitURIRequest *request = webkit_response_policy_decision_get_request(responseDecision);
419         const char *requestURI = webkit_uri_request_get_uri(request);
420         if (g_strcmp0(webkit_web_resource_get_uri(mainResource), requestURI))
421             return FALSE;
422
423         webkit_policy_decision_download(decision);
424         return TRUE;
425     }
426     case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
427     default:
428         return FALSE;
429     }
430 }
431
432 static gboolean webViewDecidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserWindow *window)
433 {
434     const gchar *dialog_title = NULL;
435     const gchar *dialog_message = NULL;
436     const gchar *dialog_message_format = NULL;
437
438     if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
439         dialog_title = "Geolocation request";
440         dialog_message_format = "%s";
441         dialog_message = "Allow geolocation request?";
442     } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) {
443         dialog_title = "Notification request";
444         dialog_message_format = "%s";
445         dialog_message = "Allow notifications request?";
446     } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
447         dialog_message_format = "Allow access to %s device?";
448         gboolean is_for_audio_device = webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request));
449         gboolean is_for_video_device = webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request));
450         dialog_title = "UserMedia request";
451         if (is_for_audio_device) {
452             if (is_for_video_device)
453                 dialog_message = "audio/video";
454             else
455                 dialog_message = "audio";
456         } else if (is_for_video_device)
457             dialog_message = "video";
458     } else if (WEBKIT_IS_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request)) {
459         dialog_title = "Media plugin missing request";
460         dialog_message_format = "The media backend was unable to find a plugin to play the requested media:\n%s.\nAllow to search and install the missing plugin?";
461         dialog_message = webkit_install_missing_media_plugins_permission_request_get_description(WEBKIT_INSTALL_MISSING_MEDIA_PLUGINS_PERMISSION_REQUEST(request));
462     } else
463         return FALSE;
464
465     GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window),
466         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
467         GTK_MESSAGE_QUESTION,
468         GTK_BUTTONS_YES_NO,
469         "%s", dialog_title);
470
471     gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), dialog_message_format, dialog_message);
472     g_signal_connect(dialog, "response", G_CALLBACK(permissionRequestDialogCallback), g_object_ref(request));
473     gtk_widget_show(dialog);
474     return TRUE;
475 }
476
477 static void webViewMouseTargetChanged(WebKitWebView *webView, WebKitHitTestResult *hitTestResult, guint mouseModifiers, BrowserWindow *window)
478 {
479     if (!webkit_hit_test_result_context_is_link(hitTestResult)) {
480         browserWindowSetStatusText(window, NULL);
481         return;
482     }
483     browserWindowSetStatusText(window, webkit_hit_test_result_get_link_uri(hitTestResult));
484 }
485
486 static gboolean browserWindowCanZoomIn(BrowserWindow *window)
487 {
488     gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep;
489     return zoomLevel < maximumZoomLevel;
490 }
491
492 static gboolean browserWindowCanZoomOut(BrowserWindow *window)
493 {
494     gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep;
495     return zoomLevel > minimumZoomLevel;
496 }
497
498 static gboolean browserWindowZoomIn(BrowserWindow *window)
499 {
500     if (browserWindowCanZoomIn(window)) {
501         gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep;
502         webkit_web_view_set_zoom_level(window->webView, zoomLevel);
503         return TRUE;
504     }
505     return FALSE;
506 }
507
508 static gboolean browserWindowZoomOut(BrowserWindow *window)
509 {
510     if (browserWindowCanZoomOut(window)) {
511         gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep;
512         webkit_web_view_set_zoom_level(window->webView, zoomLevel);
513         return TRUE;
514     }
515     return FALSE;
516 }
517
518 static gboolean scrollEventCallback(WebKitWebView *webView, const GdkEventScroll *event, BrowserWindow *window)
519 {
520     GdkModifierType mod = gtk_accelerator_get_default_mod_mask();
521
522     if ((event->state & mod) != GDK_CONTROL_MASK)
523         return FALSE;
524     
525     if (event->delta_y < 0)
526         return browserWindowZoomIn(window);
527     
528     return browserWindowZoomOut(window);
529 }
530
531 #if GTK_CHECK_VERSION(3, 12, 0)
532 static void colorChooserRGBAChanged(GtkColorChooser *colorChooser, GParamSpec *paramSpec, WebKitColorChooserRequest *request)
533 {
534     GdkRGBA rgba;
535     gtk_color_chooser_get_rgba(colorChooser, &rgba);
536     webkit_color_chooser_request_set_rgba(request, &rgba);
537 }
538
539 static void popoverColorClosed(GtkWidget *popover, WebKitColorChooserRequest *request)
540 {
541     webkit_color_chooser_request_finish(request);
542 }
543
544 static void colorChooserRequestFinished(WebKitColorChooserRequest *request, GtkWidget *popover)
545 {
546     g_object_unref(request);
547     gtk_widget_destroy(popover);
548 }
549
550 static gboolean runColorChooserCallback(WebKitWebView *webView, WebKitColorChooserRequest *request, BrowserWindow *window)
551 {
552     GtkWidget *popover = gtk_popover_new(GTK_WIDGET(webView));
553
554     GdkRectangle rectangle;
555     webkit_color_chooser_request_get_element_rectangle(request, &rectangle);
556     gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rectangle);
557
558     GtkWidget *colorChooser = gtk_color_chooser_widget_new();
559     GdkRGBA rgba;
560     webkit_color_chooser_request_get_rgba(request, &rgba);
561     gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorChooser), &rgba);
562     g_signal_connect(colorChooser, "notify::rgba", G_CALLBACK(colorChooserRGBAChanged), request);
563     gtk_container_add(GTK_CONTAINER(popover), colorChooser);
564     gtk_widget_show(colorChooser);
565
566     g_object_ref(request);
567     g_signal_connect_object(popover, "hide", G_CALLBACK(popoverColorClosed), request, 0);
568     g_signal_connect_object(request, "finished", G_CALLBACK(colorChooserRequestFinished), popover, 0);
569
570     gtk_widget_show(popover);
571
572     return TRUE;
573 }
574 #endif /* GTK_CHECK_VERSION(3, 12, 0) */
575
576 static void browserWindowUpdateZoomActions(BrowserWindow *window)
577 {
578     gtk_widget_set_sensitive(window->zoomInItem, browserWindowCanZoomIn(window));
579     gtk_widget_set_sensitive(window->zoomOutItem, browserWindowCanZoomOut(window));
580 }
581
582 static void webViewZoomLevelChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
583 {
584     browserWindowUpdateZoomActions(window);
585 }
586
587 static void updateUriEntryIcon(BrowserWindow *window)
588 {
589     GtkEntry *entry = GTK_ENTRY(window->uriEntry);
590     if (window->favicon)
591         gtk_entry_set_icon_from_pixbuf(entry, GTK_ENTRY_ICON_PRIMARY, window->favicon);
592     else
593         gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_NEW);
594 }
595
596 static void faviconChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
597 {
598     GdkPixbuf *favicon = NULL;
599     cairo_surface_t *surface = webkit_web_view_get_favicon(window->webView);
600
601     if (surface) {
602         int width = cairo_image_surface_get_width(surface);
603         int height = cairo_image_surface_get_height(surface);
604         favicon = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
605     }
606
607     if (window->favicon)
608         g_object_unref(window->favicon);
609     window->favicon = favicon;
610
611     updateUriEntryIcon(window);
612 }
613
614 static void webViewIsLoadingChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
615 {
616     gboolean isLoading = webkit_web_view_is_loading(window->webView);
617     gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(window->reloadOrStopButton), isLoading ? GTK_STOCK_STOP : GTK_STOCK_REFRESH);
618 }
619
620 static gboolean inspectorWasOpenedInAnotherWindow(WebKitWebInspector *inspectorWindow, BrowserWindow *window)
621 {
622     window->inspectorWindowIsVisible = TRUE;
623     return FALSE;
624 }
625
626 static gboolean inspectorWasClosed(WebKitWebInspector *inspectorWindow, BrowserWindow *window)
627 {
628     window->inspectorWindowIsVisible = FALSE;
629     return FALSE;
630 }
631
632 static void zoomInCallback(BrowserWindow *window)
633 {
634     browserWindowZoomIn(window);
635 }
636
637 static void zoomOutCallback(BrowserWindow *window)
638 {
639     browserWindowZoomOut(window);
640 }
641
642 static void defaultZoomCallback(BrowserWindow *window)
643 {
644     webkit_web_view_set_zoom_level(window->webView, defaultZoomLevel);
645 }
646
647 static void searchCallback(BrowserWindow *window)
648 {
649     browser_search_bar_open(window->searchBar);
650 }
651
652 static gboolean toggleWebInspector(BrowserWindow *window, gpointer user_data)
653 {
654     WebKitWebInspector *inspectorWindow;
655
656     inspectorWindow = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(window->webView));
657     if (!window->inspectorWindowIsVisible) {
658         webkit_web_inspector_show(inspectorWindow);
659         window->inspectorWindowIsVisible = TRUE;
660     } else
661         webkit_web_inspector_close(inspectorWindow);
662
663     return TRUE;
664 }
665
666 static void reloadPage(BrowserWindow *window, gpointer user_data)
667 {
668     webkit_web_view_reload(window->webView);
669 }
670
671 static void reloadPageIgnoringCache(BrowserWindow *window, gpointer user_data)
672 {
673     webkit_web_view_reload_bypass_cache(window->webView);
674 }
675
676 static void stopPageLoad(BrowserWindow *window, gpointer user_data)
677 {
678     if (gtk_widget_get_visible(GTK_WIDGET(window->searchBar))) 
679         browser_search_bar_close(window->searchBar);
680     else if (webkit_web_view_is_loading(window->webView))
681         webkit_web_view_stop_loading(window->webView);
682 }
683
684 static void loadHomePage(BrowserWindow *window, gpointer user_data)
685 {
686     webkit_web_view_load_uri(window->webView, BROWSER_DEFAULT_URL);
687 }
688
689 static gboolean toggleFullScreen(BrowserWindow *window, gpointer user_data)
690 {
691     if (!window->fullScreenIsEnabled) {
692         gtk_window_fullscreen(GTK_WINDOW(window));
693         gtk_widget_hide(window->toolbar);
694         window->fullScreenIsEnabled = TRUE;
695     } else {
696         gtk_window_unfullscreen(GTK_WINDOW(window));
697         gtk_widget_show(window->toolbar);
698         window->fullScreenIsEnabled = FALSE;
699     }
700     return TRUE;
701 }
702
703 static void editingCommandCallback(GtkWidget*widget, BrowserWindow *window)
704 {
705     webkit_web_view_execute_editing_command(window->webView, gtk_widget_get_name(widget));
706 }
707
708 static void insertImageCommandCallback(GtkWidget*widget, BrowserWindow *window)
709 {
710     GtkWidget *fileChooser = gtk_file_chooser_dialog_new("Insert Image", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
711         "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
712
713     GtkFileFilter *filter = gtk_file_filter_new();
714     gtk_file_filter_set_name(filter, "Images");
715     gtk_file_filter_add_pixbuf_formats(filter);
716     gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fileChooser), filter);
717
718     if (gtk_dialog_run(GTK_DIALOG(fileChooser)) == GTK_RESPONSE_ACCEPT) {
719         char *uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(fileChooser));
720         if (uri) {
721             webkit_web_view_execute_editing_command_with_argument(window->webView, WEBKIT_EDITING_COMMAND_INSERT_IMAGE, uri);
722             g_free(uri);
723         }
724     }
725
726     gtk_widget_destroy(fileChooser);
727 }
728
729 static void insertLinkCommandCallback(GtkWidget*widget, BrowserWindow *window)
730 {
731     GtkWidget *dialog = gtk_dialog_new_with_buttons("Insert Link", GTK_WINDOW(window), GTK_DIALOG_MODAL, "Insert", GTK_RESPONSE_ACCEPT, NULL);
732     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
733     GtkWidget *entry = gtk_entry_new();
734     gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "URL");
735     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
736     gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), entry);
737     gtk_widget_show(entry);
738
739     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
740         const char *url = gtk_entry_get_text(GTK_ENTRY(entry));
741         if (url && *url)
742             webkit_web_view_execute_editing_command_with_argument(window->webView, WEBKIT_EDITING_COMMAND_CREATE_LINK, url);
743     }
744
745     gtk_widget_destroy(dialog);
746 }
747
748 static void browserWindowEditingCommandToggleButtonSetActive(BrowserWindow *window, GtkWidget *button, gboolean active)
749 {
750     g_signal_handlers_block_by_func(button, G_CALLBACK(editingCommandCallback), window);
751     gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), active);
752     g_signal_handlers_unblock_by_func(button, G_CALLBACK(editingCommandCallback), window);
753 }
754
755 static void typingAttributesChanged(WebKitEditorState *editorState, GParamSpec *spec, BrowserWindow *window)
756 {
757     unsigned typingAttributes = webkit_editor_state_get_typing_attributes(editorState);
758     browserWindowEditingCommandToggleButtonSetActive(window, window->boldItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_BOLD);
759     browserWindowEditingCommandToggleButtonSetActive(window, window->italicItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_ITALIC);
760     browserWindowEditingCommandToggleButtonSetActive(window, window->underlineItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_UNDERLINE);
761     browserWindowEditingCommandToggleButtonSetActive(window, window->strikethroughItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_STRIKETHROUGH);
762 }
763
764 static void browserWindowFinalize(GObject *gObject)
765 {
766     BrowserWindow *window = BROWSER_WINDOW(gObject);
767     if (window->favicon) {
768         g_object_unref(window->favicon);
769         window->favicon = NULL;
770     }
771
772     if (window->accelGroup) {
773         g_object_unref(window->accelGroup);
774         window->accelGroup = NULL;
775     }
776
777     if (window->fullScreenMessageLabelId)
778         g_source_remove(window->fullScreenMessageLabelId);
779
780     if (window->resetEntryProgressTimeoutId)
781         g_source_remove(window->resetEntryProgressTimeoutId);
782
783     g_free(window->sessionFile);
784
785     G_OBJECT_CLASS(browser_window_parent_class)->finalize(gObject);
786
787     if (g_atomic_int_dec_and_test(&windowCount))
788         gtk_main_quit();
789 }
790
791 static void browserWindowGetProperty(GObject *object, guint propId, GValue *value, GParamSpec *pspec)
792 {
793     BrowserWindow *window = BROWSER_WINDOW(object);
794
795     switch (propId) {
796     case PROP_VIEW:
797         g_value_set_object(value, browser_window_get_view(window));
798         break;
799     default:
800         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
801     }
802 }
803
804 static void browserWindowSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec)
805 {
806     BrowserWindow* window = BROWSER_WINDOW(object);
807
808     switch (propId) {
809     case PROP_VIEW:
810         window->webView = g_value_get_object(value);
811         break;
812     default:
813         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
814     }
815 }
816
817 static void browserWindowSetupEditorToolbar(BrowserWindow *window)
818 {
819     GtkWidget *toolbar = gtk_toolbar_new();
820     gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
821     gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
822
823     GtkToolItem *item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_BOLD);
824     window->boldItem = GTK_WIDGET(item);
825     gtk_widget_set_name(GTK_WIDGET(item), "Bold");
826     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
827     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
828     gtk_widget_show(GTK_WIDGET(item));
829
830     item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_ITALIC);
831     window->italicItem = GTK_WIDGET(item);
832     gtk_widget_set_name(GTK_WIDGET(item), "Italic");
833     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
834     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
835     gtk_widget_show(GTK_WIDGET(item));
836
837     item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_UNDERLINE);
838     window->underlineItem = GTK_WIDGET(item);
839     gtk_widget_set_name(GTK_WIDGET(item), "Underline");
840     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
841     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
842     gtk_widget_show(GTK_WIDGET(item));
843
844     item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_STRIKETHROUGH);
845     gtk_widget_set_name(GTK_WIDGET(item), "Strikethrough");
846     window->strikethroughItem = GTK_WIDGET(item);
847     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
848     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
849     gtk_widget_show(GTK_WIDGET(item));
850
851     item = gtk_separator_tool_item_new();
852     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
853     gtk_widget_show(GTK_WIDGET(item));
854
855     item = gtk_tool_button_new_from_stock(GTK_STOCK_CUT);
856     gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_CUT);
857     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
858     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
859     gtk_widget_show(GTK_WIDGET(item));
860
861     item = gtk_tool_button_new_from_stock(GTK_STOCK_COPY);
862     gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_COPY);
863     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
864     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
865     gtk_widget_show(GTK_WIDGET(item));
866
867     item = gtk_tool_button_new_from_stock(GTK_STOCK_PASTE);
868     gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_PASTE);
869     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
870     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
871     gtk_widget_show(GTK_WIDGET(item));
872
873     item = gtk_separator_tool_item_new();
874     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
875     gtk_widget_show(GTK_WIDGET(item));
876
877     item = gtk_tool_button_new_from_stock(GTK_STOCK_UNDO);
878     gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_UNDO);
879     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
880     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
881     gtk_widget_show(GTK_WIDGET(item));
882
883     item = gtk_tool_button_new_from_stock(GTK_STOCK_REDO);
884     gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_REDO);
885     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
886     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
887     gtk_widget_show(GTK_WIDGET(item));
888
889     item = gtk_separator_tool_item_new();
890     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
891     gtk_widget_show(GTK_WIDGET(item));
892
893     item = gtk_radio_tool_button_new_from_stock(NULL, GTK_STOCK_JUSTIFY_LEFT);
894     GSList *justifyRadioGroup = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(item));
895     gtk_widget_set_name(GTK_WIDGET(item), "JustifyLeft");
896     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
897     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
898     gtk_widget_show(GTK_WIDGET(item));
899
900     item = gtk_radio_tool_button_new_from_stock(justifyRadioGroup, GTK_STOCK_JUSTIFY_CENTER);
901     justifyRadioGroup = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(item));
902     gtk_widget_set_name(GTK_WIDGET(item), "JustifyCenter");
903     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
904     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
905     gtk_widget_show(GTK_WIDGET(item));
906
907     item = gtk_radio_tool_button_new_from_stock(justifyRadioGroup, GTK_STOCK_JUSTIFY_RIGHT);
908     gtk_widget_set_name(GTK_WIDGET(item), "JustifyRight");
909     g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
910     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
911     gtk_widget_show(GTK_WIDGET(item));
912
913     item = gtk_separator_tool_item_new();
914     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
915     gtk_widget_show(GTK_WIDGET(item));
916
917     item = gtk_tool_button_new_from_stock(GTK_STOCK_INDENT);
918     gtk_widget_set_name(GTK_WIDGET(item), "Indent");
919     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
920     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
921     gtk_widget_show(GTK_WIDGET(item));
922
923     item = gtk_tool_button_new_from_stock(GTK_STOCK_UNINDENT);
924     gtk_widget_set_name(GTK_WIDGET(item), "Outdent");
925     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
926     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
927     gtk_widget_show(GTK_WIDGET(item));
928
929     item = gtk_separator_tool_item_new();
930     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
931     gtk_widget_show(GTK_WIDGET(item));
932
933     item = gtk_tool_button_new(NULL, NULL);
934     gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "insert-image");
935     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(insertImageCommandCallback), window);
936     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
937     gtk_widget_show(GTK_WIDGET(item));
938
939     item = gtk_tool_button_new(NULL, NULL);
940     gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "insert-link");
941     g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(insertLinkCommandCallback), window);
942     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
943     gtk_widget_show(GTK_WIDGET(item));
944
945     gtk_box_pack_start(GTK_BOX(window->mainBox), toolbar, FALSE, FALSE, 0);
946     gtk_widget_show(toolbar);
947 }
948
949 static void browser_window_init(BrowserWindow *window)
950 {
951     g_atomic_int_inc(&windowCount);
952
953     gtk_window_set_title(GTK_WINDOW(window), defaultWindowTitle);
954     gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
955
956     window->uriEntry = gtk_entry_new();
957     g_signal_connect_swapped(window->uriEntry, "activate", G_CALLBACK(activateUriEntryCallback), (gpointer)window);
958     gtk_entry_set_icon_activatable(GTK_ENTRY(window->uriEntry), GTK_ENTRY_ICON_PRIMARY, FALSE);
959     updateUriEntryIcon(window);
960
961     /* Keyboard accelerators */
962     window->accelGroup = gtk_accel_group_new();
963     gtk_window_add_accel_group(GTK_WINDOW(window), window->accelGroup);
964
965     /* Global accelerators */
966     gtk_accel_group_connect(window->accelGroup, GDK_KEY_I, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE,
967         g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL));
968     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F12, 0, GTK_ACCEL_VISIBLE,
969         g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL));
970
971     /* Reload page */ 
972     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F5, 0, GTK_ACCEL_VISIBLE,
973         g_cclosure_new_swap(G_CALLBACK(reloadPage), window, NULL));
974     gtk_accel_group_connect(window->accelGroup, GDK_KEY_R, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
975         g_cclosure_new_swap(G_CALLBACK(reloadPage), window, NULL));
976
977     /* Reload page ignoring cache */
978     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F5, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
979         g_cclosure_new_swap(G_CALLBACK(reloadPageIgnoringCache), window, NULL));
980     gtk_accel_group_connect(window->accelGroup, GDK_KEY_R, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE,
981         g_cclosure_new_swap(G_CALLBACK(reloadPageIgnoringCache), window, NULL));
982
983     /* Stop page load */ 
984     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F6, 0, GTK_ACCEL_VISIBLE,
985         g_cclosure_new_swap(G_CALLBACK(stopPageLoad), window, NULL));
986     gtk_accel_group_connect(window->accelGroup, GDK_KEY_Escape, 0, GTK_ACCEL_VISIBLE,
987         g_cclosure_new_swap(G_CALLBACK(stopPageLoad), window, NULL));
988
989     /* Load home page */ 
990     gtk_accel_group_connect(window->accelGroup, GDK_KEY_Home, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE,
991         g_cclosure_new_swap(G_CALLBACK(loadHomePage), window, NULL));
992
993     /* Zoom in, zoom out and default zoom*/
994     gtk_accel_group_connect(window->accelGroup, GDK_KEY_equal, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
995         g_cclosure_new_swap(G_CALLBACK(zoomInCallback), window, NULL));
996     gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_Add, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
997         g_cclosure_new_swap(G_CALLBACK(zoomInCallback), window, NULL));
998     gtk_accel_group_connect(window->accelGroup, GDK_KEY_minus, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
999         g_cclosure_new_swap(G_CALLBACK(zoomOutCallback), window, NULL));
1000     gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
1001         g_cclosure_new_swap(G_CALLBACK(zoomOutCallback), window, NULL));
1002     gtk_accel_group_connect(window->accelGroup, GDK_KEY_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
1003         g_cclosure_new_swap(G_CALLBACK(defaultZoomCallback), window, NULL));
1004     gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
1005         g_cclosure_new_swap(G_CALLBACK(defaultZoomCallback), window, NULL));
1006
1007     /* Toggle fullscreen */ 
1008     gtk_accel_group_connect(window->accelGroup, GDK_KEY_F11, 0, GTK_ACCEL_VISIBLE,
1009         g_cclosure_new_swap(G_CALLBACK(toggleFullScreen), window, NULL));
1010
1011     /* Quit */
1012     gtk_accel_group_connect(window->accelGroup, GDK_KEY_Q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
1013         g_cclosure_new_swap(G_CALLBACK(gtk_widget_destroy), window, NULL));
1014     gtk_accel_group_connect(window->accelGroup, GDK_KEY_W, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
1015         g_cclosure_new_swap(G_CALLBACK(gtk_widget_destroy), window, NULL));
1016
1017     GtkWidget *toolbar = gtk_toolbar_new();
1018     window->toolbar = toolbar;
1019     gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
1020     gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1021
1022     GtkToolItem *item = gtk_menu_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
1023     window->backItem = GTK_WIDGET(item);
1024     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(item), 0);
1025     g_signal_connect_swapped(item, "clicked", G_CALLBACK(goBackCallback), (gpointer)window);
1026     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1027     gtk_widget_show(GTK_WIDGET(item));
1028
1029     item = gtk_menu_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
1030     window->forwardItem = GTK_WIDGET(item);
1031     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(item), 0);
1032     g_signal_connect_swapped(G_OBJECT(item), "clicked", G_CALLBACK(goForwardCallback), (gpointer)window);
1033     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1034     gtk_widget_show(GTK_WIDGET(item));
1035
1036     item = gtk_tool_button_new_from_stock(GTK_STOCK_PREFERENCES);
1037     g_signal_connect_swapped(G_OBJECT(item), "clicked", G_CALLBACK(settingsCallback), window);
1038     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1039     gtk_widget_show(GTK_WIDGET(item));
1040
1041     item = gtk_tool_button_new_from_stock(GTK_STOCK_ZOOM_OUT);
1042     window->zoomOutItem = GTK_WIDGET(item);
1043     g_signal_connect_swapped(item, "clicked", G_CALLBACK(zoomOutCallback), window);
1044     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1045     gtk_widget_show(GTK_WIDGET(item));
1046
1047     item = gtk_tool_button_new_from_stock(GTK_STOCK_ZOOM_IN);
1048     window->zoomInItem = GTK_WIDGET(item);
1049     g_signal_connect_swapped(item, "clicked", G_CALLBACK(zoomInCallback), window);
1050     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1051     gtk_widget_show(GTK_WIDGET(item));
1052
1053     item = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
1054     g_signal_connect_swapped(item, "clicked", G_CALLBACK(searchCallback), window);
1055     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1056     gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_F, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
1057     gtk_widget_show(GTK_WIDGET(item));
1058
1059     item = gtk_tool_button_new_from_stock(GTK_STOCK_HOME);
1060     g_signal_connect_swapped(item, "clicked", G_CALLBACK(loadHomePage), window);
1061     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1062     gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_Home, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE);
1063     gtk_widget_show(GTK_WIDGET(item));
1064
1065     item = gtk_tool_item_new();
1066     gtk_tool_item_set_expand(item, TRUE);
1067     gtk_container_add(GTK_CONTAINER(item), window->uriEntry);
1068     gtk_widget_show(window->uriEntry);
1069     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1070     gtk_widget_show(GTK_WIDGET(item));
1071
1072     item = gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH);
1073     window->reloadOrStopButton = GTK_WIDGET(item);
1074     g_signal_connect_swapped(item, "clicked", G_CALLBACK(reloadOrStopCallback), window);
1075     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1076     gtk_widget_add_accelerator(window->reloadOrStopButton, "clicked", window->accelGroup, GDK_KEY_F5, 0, GTK_ACCEL_VISIBLE);
1077     gtk_widget_show(window->reloadOrStopButton);
1078
1079     GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1080     window->mainBox = vbox;
1081     gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
1082     gtk_widget_show(toolbar);
1083
1084     gtk_container_add(GTK_CONTAINER(window), vbox);
1085     gtk_widget_show(vbox);
1086 }
1087
1088 static void browserWindowConstructed(GObject *gObject)
1089 {
1090     BrowserWindow *window = BROWSER_WINDOW(gObject);
1091
1092     browserWindowUpdateZoomActions(window);
1093     if (webkit_web_view_is_editable(window->webView)) {
1094         browserWindowSetupEditorToolbar(window);
1095         g_signal_connect(webkit_web_view_get_editor_state(window->webView), "notify::typing-attributes", G_CALLBACK(typingAttributesChanged), window);
1096     }
1097
1098     g_signal_connect(window->webView, "notify::uri", G_CALLBACK(webViewURIChanged), window);
1099     g_signal_connect(window->webView, "notify::estimated-load-progress", G_CALLBACK(webViewLoadProgressChanged), window);
1100     g_signal_connect(window->webView, "notify::title", G_CALLBACK(webViewTitleChanged), window);
1101     g_signal_connect(window->webView, "create", G_CALLBACK(webViewCreate), window);
1102     g_signal_connect(window->webView, "close", G_CALLBACK(webViewClose), window);
1103     g_signal_connect(window->webView, "load-failed", G_CALLBACK(webViewLoadFailed), window);
1104     g_signal_connect(window->webView, "load-failed-with-tls-errors", G_CALLBACK(webViewLoadFailedWithTLSerrors), window);
1105     g_signal_connect(window->webView, "decide-policy", G_CALLBACK(webViewDecidePolicy), window);
1106     g_signal_connect(window->webView, "permission-request", G_CALLBACK(webViewDecidePermissionRequest), window);
1107     g_signal_connect(window->webView, "mouse-target-changed", G_CALLBACK(webViewMouseTargetChanged), window);
1108     g_signal_connect(window->webView, "notify::zoom-level", G_CALLBACK(webViewZoomLevelChanged), window);
1109     g_signal_connect(window->webView, "notify::favicon", G_CALLBACK(faviconChanged), window);
1110     g_signal_connect(window->webView, "enter-fullscreen", G_CALLBACK(webViewEnterFullScreen), window);
1111     g_signal_connect(window->webView, "leave-fullscreen", G_CALLBACK(webViewLeaveFullScreen), window);
1112     g_signal_connect(window->webView, "notify::is-loading", G_CALLBACK(webViewIsLoadingChanged), window);
1113     g_signal_connect(window->webView, "scroll-event", G_CALLBACK(scrollEventCallback), window);
1114 #if GTK_CHECK_VERSION(3, 12, 0)
1115     g_signal_connect(window->webView, "run-color-chooser", G_CALLBACK(runColorChooserCallback), window);
1116 #endif
1117
1118     g_signal_connect(webkit_web_view_get_context(window->webView), "download-started", G_CALLBACK(downloadStarted), window);
1119
1120     window->searchBar = BROWSER_SEARCH_BAR(browser_search_bar_new(window->webView));
1121     browser_search_bar_add_accelerators(window->searchBar, window->accelGroup);
1122     gtk_box_pack_start(GTK_BOX(window->mainBox), GTK_WIDGET(window->searchBar), FALSE, FALSE, 0);
1123
1124     WebKitBackForwardList *backForwadlist = webkit_web_view_get_back_forward_list(window->webView);
1125     g_signal_connect(backForwadlist, "changed", G_CALLBACK(backForwadlistChanged), window);
1126
1127     WebKitWebInspector *inspectorWindow = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(window->webView));
1128     g_signal_connect(inspectorWindow, "open-window", G_CALLBACK(inspectorWasOpenedInAnotherWindow), window);
1129     g_signal_connect(inspectorWindow, "closed", G_CALLBACK(inspectorWasClosed), window);
1130
1131     GtkWidget *overlay = gtk_overlay_new();
1132     gtk_box_pack_start(GTK_BOX(window->mainBox), overlay, TRUE, TRUE, 0);
1133     gtk_widget_show(overlay);
1134
1135     window->statusLabel = gtk_label_new(NULL);
1136     gtk_widget_set_halign(window->statusLabel, GTK_ALIGN_START);
1137     gtk_widget_set_valign(window->statusLabel, GTK_ALIGN_END);
1138     gtk_widget_set_margin_left(window->statusLabel, 1);
1139     gtk_widget_set_margin_right(window->statusLabel, 1);
1140     gtk_widget_set_margin_top(window->statusLabel, 1);
1141     gtk_widget_set_margin_bottom(window->statusLabel, 1);
1142     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), window->statusLabel);
1143
1144     gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(window->webView));
1145
1146     window->fullScreenMessageLabel = gtk_label_new(NULL);
1147     gtk_widget_set_halign(window->fullScreenMessageLabel, GTK_ALIGN_CENTER);
1148     gtk_widget_set_valign(window->fullScreenMessageLabel, GTK_ALIGN_CENTER);
1149     gtk_widget_set_no_show_all(window->fullScreenMessageLabel, TRUE);
1150     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), window->fullScreenMessageLabel);
1151     gtk_widget_show(GTK_WIDGET(window->webView));
1152
1153     if (webkit_web_view_is_editable(window->webView))
1154         webkit_web_view_load_html(window->webView, "<html></html>", "file:///");
1155 }
1156
1157 static void browserWindowSaveSession(BrowserWindow *window)
1158 {
1159     if (!window->sessionFile)
1160         return;
1161
1162     WebKitWebViewSessionState *state = webkit_web_view_get_session_state(window->webView);
1163     GBytes *bytes = webkit_web_view_session_state_serialize(state);
1164     webkit_web_view_session_state_unref(state);
1165     g_file_set_contents(window->sessionFile, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), NULL);
1166     g_bytes_unref(bytes);
1167 }
1168
1169 static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event)
1170 {
1171     BrowserWindow *window = BROWSER_WINDOW(widget);
1172     browserWindowSaveSession(window);
1173     webkit_web_view_try_close(window->webView);
1174     return TRUE;
1175 }
1176
1177 static void browser_window_class_init(BrowserWindowClass *klass)
1178 {
1179     GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
1180
1181     gobjectClass->constructed = browserWindowConstructed;
1182     gobjectClass->get_property = browserWindowGetProperty;
1183     gobjectClass->set_property = browserWindowSetProperty;
1184     gobjectClass->finalize = browserWindowFinalize;
1185
1186     GtkWidgetClass *widgetClass = GTK_WIDGET_CLASS(klass);
1187     widgetClass->delete_event = browserWindowDeleteEvent;
1188
1189     g_object_class_install_property(gobjectClass,
1190                                     PROP_VIEW,
1191                                     g_param_spec_object("view",
1192                                                         "View",
1193                                                         "The web view of this window",
1194                                                         WEBKIT_TYPE_WEB_VIEW,
1195                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1196 }
1197
1198 // Public API.
1199 GtkWidget *browser_window_new(WebKitWebView *view, GtkWindow *parent)
1200 {
1201     g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), 0);
1202
1203     BrowserWindow *window = BROWSER_WINDOW(g_object_new(BROWSER_TYPE_WINDOW,
1204         "type", GTK_WINDOW_TOPLEVEL, "view", view, NULL));
1205
1206     if (parent) {
1207         window->parentWindow = parent;
1208         g_object_add_weak_pointer(G_OBJECT(parent), (gpointer *)&window->parentWindow);
1209     }
1210
1211     return GTK_WIDGET(window);
1212 }
1213
1214 WebKitWebView *browser_window_get_view(BrowserWindow *window)
1215 {
1216     g_return_val_if_fail(BROWSER_IS_WINDOW(window), 0);
1217
1218     return window->webView;
1219 }
1220
1221 void browser_window_load_uri(BrowserWindow *window, const char *uri)
1222 {
1223     g_return_if_fail(BROWSER_IS_WINDOW(window));
1224     g_return_if_fail(uri);
1225
1226     if (!g_str_has_prefix(uri, "javascript:")) {
1227         char *internalURI = getInternalURI(uri);
1228         webkit_web_view_load_uri(window->webView, internalURI);
1229         g_free(internalURI);
1230         return;
1231     }
1232
1233     webkit_web_view_run_javascript(window->webView, strstr(uri, "javascript:"), NULL, NULL, NULL);
1234 }
1235
1236 void browser_window_load_session(BrowserWindow *window, const char *sessionFile)
1237 {
1238     g_return_if_fail(BROWSER_IS_WINDOW(window));
1239     g_return_if_fail(sessionFile);
1240
1241     window->sessionFile = g_strdup(sessionFile);
1242     gchar *data = NULL;
1243     gsize dataLength;
1244     if (g_file_get_contents(sessionFile, &data, &dataLength, NULL)) {
1245         GBytes *bytes = g_bytes_new_take(data, dataLength);
1246         WebKitWebViewSessionState *state = webkit_web_view_session_state_new(bytes);
1247         g_bytes_unref(bytes);
1248
1249         if (state) {
1250             webkit_web_view_restore_session_state(window->webView, state);
1251             webkit_web_view_session_state_unref(state);
1252         }
1253     }
1254
1255     WebKitBackForwardList *bfList = webkit_web_view_get_back_forward_list(window->webView);
1256     WebKitBackForwardListItem *item = webkit_back_forward_list_get_current_item(bfList);
1257     if (item)
1258         webkit_web_view_go_to_back_forward_list_item(window->webView, item);
1259     else
1260         webkit_web_view_load_uri(window->webView, BROWSER_DEFAULT_URL);
1261
1262 }
1263
1264 void browser_window_set_background_color(BrowserWindow *window, GdkRGBA *rgba)
1265 {
1266     g_return_if_fail(BROWSER_IS_WINDOW(window));
1267     g_return_if_fail(rgba);
1268
1269     GdkRGBA viewRGBA;
1270     webkit_web_view_get_background_color(window->webView, &viewRGBA);
1271     if (gdk_rgba_equal(rgba, &viewRGBA))
1272         return;
1273
1274     if (rgba->alpha < 1) {
1275         GdkVisual *rgbaVisual = gdk_screen_get_rgba_visual(gtk_window_get_screen(GTK_WINDOW(window)));
1276         if (!rgbaVisual)
1277             return;
1278
1279         gtk_widget_set_visual(GTK_WIDGET(window), rgbaVisual);
1280         gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
1281     }
1282
1283     webkit_web_view_set_background_color(window->webView, rgba);
1284 }