[GTK] Add support to load/save session in MiniBrowser
[WebKit-https.git] / Tools / MiniBrowser / gtk / main.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. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "cmakeconfig.h"
29
30 #include "BrowserWindow.h"
31 #include <errno.h>
32 #include <gtk/gtk.h>
33 #include <string.h>
34 #include <webkit2/webkit2.h>
35
36 #define MINI_BROWSER_ERROR (miniBrowserErrorQuark())
37
38 static const gchar **uriArguments = NULL;
39 static const char *miniBrowserAboutScheme = "minibrowser-about";
40 static GdkRGBA *backgroundColor;
41 static gboolean editorMode;
42 static const char *sessionFile;
43
44 typedef enum {
45     MINI_BROWSER_ERROR_INVALID_ABOUT_PATH
46 } MiniBrowserError;
47
48 static GQuark miniBrowserErrorQuark()
49 {
50     return g_quark_from_string("minibrowser-quark");
51 }
52
53 static gchar *argumentToURL(const char *filename)
54 {
55     GFile *gfile = g_file_new_for_commandline_arg(filename);
56     gchar *fileURL = g_file_get_uri(gfile);
57     g_object_unref(gfile);
58
59     return fileURL;
60 }
61
62 static void createBrowserWindow(const gchar *uri, WebKitSettings *webkitSettings, gboolean shouldLoadSession)
63 {
64     GtkWidget *webView = webkit_web_view_new();
65     if (editorMode)
66         webkit_web_view_set_editable(WEBKIT_WEB_VIEW(webView), TRUE);
67     GtkWidget *mainWindow = browser_window_new(WEBKIT_WEB_VIEW(webView), NULL);
68     if (backgroundColor)
69         browser_window_set_background_color(BROWSER_WINDOW(mainWindow), backgroundColor);
70
71     if (webkitSettings)
72         webkit_web_view_set_settings(WEBKIT_WEB_VIEW(webView), webkitSettings);
73
74     if (!editorMode) {
75         if (shouldLoadSession && sessionFile)
76             browser_window_load_session(BROWSER_WINDOW(mainWindow), sessionFile);
77         else {
78             gchar *url = argumentToURL(uri);
79             browser_window_load_uri(BROWSER_WINDOW(mainWindow), url);
80             g_free(url);
81         }
82     }
83
84     gtk_widget_grab_focus(webView);
85     gtk_widget_show(mainWindow);
86 }
87
88 static gboolean parseBackgroundColor(const char *optionName, const char *value, gpointer data, GError **error)
89 {
90     GdkRGBA rgba;
91     if (gdk_rgba_parse(&rgba, value)) {
92         backgroundColor = gdk_rgba_copy(&rgba);
93         return TRUE;
94     }
95
96     g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Failed to parse '%s' as RGBA color", value);
97     return FALSE;
98 }
99
100 static const GOptionEntry commandLineOptions[] =
101 {
102     { "bg-color", 0, 0, G_OPTION_ARG_CALLBACK, parseBackgroundColor, "Background color", NULL },
103     { "editor-mode", 'e', 0, G_OPTION_ARG_NONE, &editorMode, "Run in editor mode", NULL },
104     { "session-file", 's', 0, G_OPTION_ARG_FILENAME, &sessionFile, "Session file", "FILE" },
105     { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &uriArguments, 0, "[URL…]" },
106     { 0, 0, 0, 0, 0, 0, 0 }
107 };
108
109 static gboolean parseOptionEntryCallback(const gchar *optionNameFull, const gchar *value, WebKitSettings *webSettings, GError **error)
110 {
111     if (strlen(optionNameFull) <= 2) {
112         g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid option %s", optionNameFull);
113         return FALSE;
114     }
115
116     /* We have two -- in option name so remove them. */
117     const gchar *optionName = optionNameFull + 2;
118     GParamSpec *spec = g_object_class_find_property(G_OBJECT_GET_CLASS(webSettings), optionName);
119     if (!spec) {
120         g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Cannot find web settings for option %s", optionNameFull);
121         return FALSE;
122     }
123
124     switch (G_PARAM_SPEC_VALUE_TYPE(spec)) {
125     case G_TYPE_BOOLEAN: {
126         gboolean propertyValue = !(value && g_ascii_strcasecmp(value, "true") && strcmp(value, "1"));
127         g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL);
128         break;
129     }
130     case G_TYPE_STRING:
131         g_object_set(G_OBJECT(webSettings), optionName, value, NULL);
132         break;
133     case G_TYPE_INT: {
134         glong propertyValue;
135         gchar *end;
136
137         errno = 0;
138         propertyValue = g_ascii_strtoll(value, &end, 0);
139         if (errno == ERANGE || propertyValue > G_MAXINT || propertyValue < G_MININT) {
140             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Integer value '%s' for %s out of range", value, optionNameFull);
141             return FALSE;
142         }
143         if (errno || value == end) {
144             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Cannot parse integer value '%s' for %s", value, optionNameFull);
145             return FALSE;
146         }
147         g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL);
148         break;
149     }
150     case G_TYPE_FLOAT: {
151         gdouble propertyValue;
152         gchar *end;
153
154         errno = 0;
155         propertyValue = g_ascii_strtod(value, &end);
156         if (errno == ERANGE || propertyValue > G_MAXFLOAT || propertyValue < G_MINFLOAT) {
157             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Float value '%s' for %s out of range", value, optionNameFull);
158             return FALSE;
159         }
160         if (errno || value == end) {
161             g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Cannot parse float value '%s' for %s", value, optionNameFull);
162             return FALSE;
163         }
164         g_object_set(G_OBJECT(webSettings), optionName, propertyValue, NULL);
165         break;
166     }
167     default:
168         g_assert_not_reached();
169     }
170
171     return TRUE;
172 }
173
174 static gboolean isValidParameterType(GType gParamType)
175 {
176     return (gParamType == G_TYPE_BOOLEAN || gParamType == G_TYPE_STRING || gParamType == G_TYPE_INT
177             || gParamType == G_TYPE_FLOAT);
178 }
179
180 static GOptionEntry* getOptionEntriesFromWebKitSettings(WebKitSettings *webSettings)
181 {
182     GParamSpec **propertySpecs;
183     GOptionEntry *optionEntries;
184     guint numProperties, numEntries, i;
185
186     propertySpecs = g_object_class_list_properties(G_OBJECT_GET_CLASS(webSettings), &numProperties);
187     if (!propertySpecs)
188         return NULL;
189
190     optionEntries = g_new0(GOptionEntry, numProperties + 1);
191     numEntries = 0;
192     for (i = 0; i < numProperties; i++) {
193         GParamSpec *param = propertySpecs[i];
194
195         /* Fill in structures only for writable and not construct-only properties. */
196         if (!param || !(param->flags & G_PARAM_WRITABLE) || (param->flags & G_PARAM_CONSTRUCT_ONLY))
197             continue;
198
199         GType gParamType = G_PARAM_SPEC_VALUE_TYPE(param);
200         if (!isValidParameterType(gParamType))
201             continue;
202
203         GOptionEntry *optionEntry = &optionEntries[numEntries++];
204         optionEntry->long_name = g_param_spec_get_name(param);
205
206         /* There is no easy way to figure our short name for generated option entries.
207            optionEntry.short_name=*/
208         /* For bool arguments "enable" type make option argument not required. */
209         if (gParamType == G_TYPE_BOOLEAN && (strstr(optionEntry->long_name, "enable")))
210             optionEntry->flags = G_OPTION_FLAG_OPTIONAL_ARG;
211         optionEntry->arg = G_OPTION_ARG_CALLBACK;
212         optionEntry->arg_data = parseOptionEntryCallback;
213         optionEntry->description = g_param_spec_get_blurb(param);
214         optionEntry->arg_description = g_type_name(gParamType);
215     }
216     g_free(propertySpecs);
217
218     return optionEntries;
219 }
220
221 static gboolean addSettingsGroupToContext(GOptionContext *context, WebKitSettings* webkitSettings)
222 {
223     GOptionEntry *optionEntries = getOptionEntriesFromWebKitSettings(webkitSettings);
224     if (!optionEntries)
225         return FALSE;
226
227     GOptionGroup *webSettingsGroup = g_option_group_new("websettings",
228                                                         "WebKitSettings writable properties for default WebKitWebView",
229                                                         "WebKitSettings properties",
230                                                         webkitSettings,
231                                                         NULL);
232     g_option_group_add_entries(webSettingsGroup, optionEntries);
233     g_free(optionEntries);
234
235     /* Option context takes ownership of the group. */
236     g_option_context_add_group(context, webSettingsGroup);
237
238     return TRUE;
239 }
240
241 static void
242 aboutURISchemeRequestCallback(WebKitURISchemeRequest *request, gpointer userData)
243 {
244     GInputStream *stream;
245     gsize streamLength;
246     const gchar *path;
247     gchar *contents;
248     GError *error;
249
250     path = webkit_uri_scheme_request_get_path(request);
251     if (!g_strcmp0(path, "minibrowser")) {
252         contents = g_strdup_printf("<html><body><h1>WebKitGTK+ MiniBrowser</h1><p>The WebKit2 test browser of the GTK+ port.</p><p>WebKit version: %d.%d.%d</p></body></html>",
253             webkit_get_major_version(),
254             webkit_get_minor_version(),
255             webkit_get_micro_version());
256         streamLength = strlen(contents);
257         stream = g_memory_input_stream_new_from_data(contents, streamLength, g_free);
258
259         webkit_uri_scheme_request_finish(request, stream, streamLength, "text/html");
260         g_object_unref(stream);
261     } else {
262         error = g_error_new(MINI_BROWSER_ERROR, MINI_BROWSER_ERROR_INVALID_ABOUT_PATH, "Invalid about:%s page.", path);
263         webkit_uri_scheme_request_finish_error(request, error);
264         g_error_free(error);
265     }
266 }
267
268 int main(int argc, char *argv[])
269 {
270     gtk_init(&argc, &argv);
271 #if ENABLE_DEVELOPER_MODE
272     g_setenv("WEBKIT_INJECTED_BUNDLE_PATH", WEBKIT_INJECTED_BUNDLE_PATH, FALSE);
273 #endif
274
275     const gchar *singleprocess = g_getenv("MINIBROWSER_SINGLEPROCESS");
276     webkit_web_context_set_process_model(webkit_web_context_get_default(), (singleprocess && *singleprocess) ?
277         WEBKIT_PROCESS_MODEL_SHARED_SECONDARY_PROCESS : WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
278
279     GOptionContext *context = g_option_context_new(NULL);
280     g_option_context_add_main_entries(context, commandLineOptions, 0);
281     g_option_context_add_group(context, gtk_get_option_group(TRUE));
282
283     WebKitSettings *webkitSettings = webkit_settings_new();
284     webkit_settings_set_enable_developer_extras(webkitSettings, TRUE);
285     webkit_settings_set_enable_webgl(webkitSettings, TRUE);
286     if (!addSettingsGroupToContext(context, webkitSettings))
287         g_clear_object(&webkitSettings);
288
289     GError *error = 0;
290     if (!g_option_context_parse(context, &argc, &argv, &error)) {
291         g_printerr("Cannot parse arguments: %s\n", error->message);
292         g_error_free(error);
293         g_option_context_free(context);
294
295         return 1;
296     }
297     g_option_context_free (context);
298
299     // Enable the favicon database, by specifying the default directory.
300     webkit_web_context_set_favicon_database_directory(webkit_web_context_get_default(), NULL);
301
302     webkit_web_context_register_uri_scheme(webkit_web_context_get_default(), miniBrowserAboutScheme, aboutURISchemeRequestCallback, NULL, NULL);
303
304     if (uriArguments) {
305         int i;
306
307         for (i = 0; uriArguments[i]; i++)
308             createBrowserWindow(uriArguments[i], webkitSettings, FALSE);
309     } else
310         createBrowserWindow(BROWSER_DEFAULT_URL, webkitSettings, TRUE);
311
312     g_clear_object(&webkitSettings);
313
314     gtk_main();
315
316     return 0;
317 }