The "load-{start,committed,..}" webview signals are deprecated
[luakit.git] / widgets / webview.c
blob7ac1f2e8612adb9703d07529a1fc3e7b768e2998
1 /*
2 * webview.c - webkit webview widget
4 * Copyright (C) 2010 Mason Larobina <mason.larobina@gmail.com>
5 * Copyright (C) 2007-2009 Julien Danjou <julien@danjou.info>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "luah.h"
23 #include "widgets/common.h"
24 #include <JavaScriptCore/JavaScript.h>
25 #include <webkit/webkit.h>
26 #include <libsoup/soup.h>
27 #include "math.h"
29 static struct {
30 SoupSession *session;
31 SoupCookieJar *cookiejar;
32 } Soup = { NULL, NULL };
34 typedef enum { BOOL, CHAR, INT, FLOAT, DOUBLE, URI } property_value_type;
35 typedef enum { SETTINGS, WEBKITVIEW, SOUPSESSION } property_value_scope;
37 typedef union {
38 gchar *c;
39 gboolean b;
40 gdouble d;
41 gfloat f;
42 gint i;
43 } property_tmp_values;
45 static const struct {
46 const gchar *name;
47 property_value_type type;
48 property_value_scope scope;
49 gboolean writable;
50 } properties[] = {
51 { "accept-language", CHAR, SOUPSESSION, TRUE },
52 { "accept-language-auto", BOOL, SOUPSESSION, TRUE },
53 { "auto-load-images", BOOL, SETTINGS, TRUE },
54 { "auto-resize-window", BOOL, SETTINGS, TRUE },
55 { "auto-shrink-images", BOOL, SETTINGS, TRUE },
56 { "cursive-font-family", CHAR, SETTINGS, TRUE },
57 { "custom-encoding", CHAR, WEBKITVIEW, TRUE },
58 { "default-encoding", CHAR, SETTINGS, TRUE },
59 { "default-font-family", CHAR, SETTINGS, TRUE },
60 { "default-font-size", INT, SETTINGS, TRUE },
61 { "default-monospace-font-size", INT, SETTINGS, TRUE },
62 { "editable", BOOL, WEBKITVIEW, TRUE },
63 { "enable-caret-browsing", BOOL, SETTINGS, TRUE },
64 { "enable-default-context-menu", BOOL, SETTINGS, TRUE },
65 { "enable-developer-extras", BOOL, SETTINGS, TRUE },
66 { "enable-dom-paste", BOOL, SETTINGS, TRUE },
67 { "enable-file-access-from-file-uris", BOOL, SETTINGS, TRUE },
68 { "enable-html5-database", BOOL, SETTINGS, TRUE },
69 { "enable-html5-local-storage", BOOL, SETTINGS, TRUE },
70 { "enable-java-applet", BOOL, SETTINGS, TRUE },
71 { "enable-offline-web-application-cache", BOOL, SETTINGS, TRUE },
72 { "enable-page-cache", BOOL, SETTINGS, TRUE },
73 { "enable-plugins", BOOL, SETTINGS, TRUE },
74 { "enable-private-browsing", BOOL, SETTINGS, TRUE },
75 { "enable-scripts", BOOL, SETTINGS, TRUE },
76 { "enable-site-specific-quirks", BOOL, SETTINGS, TRUE },
77 { "enable-spatial-navigation", BOOL, SETTINGS, TRUE },
78 { "enable-spell-checking", BOOL, SETTINGS, TRUE },
79 { "enable-universal-access-from-file-uris", BOOL, SETTINGS, TRUE },
80 { "enable-xss-auditor", BOOL, SETTINGS, TRUE },
81 { "encoding", CHAR, WEBKITVIEW, FALSE },
82 { "enforce-96-dpi", BOOL, SETTINGS, TRUE },
83 { "fantasy-font-family", CHAR, SETTINGS, TRUE },
84 { "full-content-zoom", BOOL, WEBKITVIEW, TRUE },
85 { "idle-timeout", INT, SOUPSESSION, TRUE },
86 { "icon-uri", CHAR, WEBKITVIEW, FALSE },
87 { "javascript-can-access-clipboard", BOOL, SETTINGS, TRUE },
88 { "javascript-can-open-windows-automatically", BOOL, SETTINGS, TRUE },
89 { "max-conns", INT, SOUPSESSION, TRUE },
90 { "max-conns-per-host", INT, SOUPSESSION, TRUE },
91 { "minimum-font-size", INT, SETTINGS, TRUE },
92 { "minimum-logical-font-size", INT, SETTINGS, TRUE },
93 { "monospace-font-family", CHAR, SETTINGS, TRUE },
94 { "print-backgrounds", BOOL, SETTINGS, TRUE },
95 { "progress", DOUBLE, WEBKITVIEW, FALSE },
96 { "proxy-uri", URI, SOUPSESSION, TRUE },
97 { "resizable-text-areas", BOOL, SETTINGS, TRUE },
98 { "sans-serif-font-family", CHAR, SETTINGS, TRUE },
99 { "serif-font-family", CHAR, SETTINGS, TRUE },
100 { "spell-checking-languages", CHAR, SETTINGS, TRUE },
101 { "ssl-ca-file", CHAR, SOUPSESSION, TRUE },
102 { "ssl-strict", BOOL, SOUPSESSION, TRUE },
103 { "tab-key-cycles-through-elements", BOOL, SETTINGS, TRUE },
104 { "timeout", INT, SOUPSESSION, TRUE },
105 { "title", CHAR, WEBKITVIEW, FALSE },
106 { "transparent", BOOL, WEBKITVIEW, TRUE },
107 { "use-ntlm", BOOL, SOUPSESSION, TRUE },
108 { "user-agent", CHAR, SETTINGS, TRUE },
109 { "user-stylesheet-uri", CHAR, SETTINGS, TRUE },
110 { "zoom-level", FLOAT, WEBKITVIEW, TRUE },
111 { "zoom-step", FLOAT, SETTINGS, TRUE },
112 { NULL, 0, 0, 0 },
115 static const struct {
116 WebKitLoadStatus atom;
117 const gchar *name;
118 } load_status_names[] = {
119 { WEBKIT_LOAD_PROVISIONAL, "provisional" },
120 { WEBKIT_LOAD_COMMITTED, "committed", },
121 { WEBKIT_LOAD_FINISHED, "finished" },
122 { WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT, "first-visual" },
123 { WEBKIT_LOAD_FAILED, "failed" },
124 { 0, NULL, },
127 static const gchar*
128 webview_eval_js(WebKitWebView *view, const gchar *script, const gchar *file) {
129 WebKitWebFrame *frame;
130 JSGlobalContextRef context;
131 JSObjectRef globalobject;
132 JSStringRef js_file;
133 JSStringRef js_script;
134 JSValueRef js_result;
135 JSValueRef js_exc = NULL;
136 JSStringRef js_result_string;
137 GString *result = g_string_new(NULL);
138 size_t js_result_size;
140 frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(view));
141 context = webkit_web_frame_get_global_context(frame);
142 globalobject = JSContextGetGlobalObject(context);
144 /* evaluate the script and get return value*/
145 js_script = JSStringCreateWithUTF8CString(script);
146 js_file = JSStringCreateWithUTF8CString(file);
147 js_result = JSEvaluateScript(context, js_script, globalobject, js_file, 0, &js_exc);
148 if (js_result && !JSValueIsUndefined(context, js_result)) {
149 js_result_string = JSValueToStringCopy(context, js_result, NULL);
150 js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string);
152 if (js_result_size) {
153 char js_result_utf8[js_result_size];
154 JSStringGetUTF8CString(js_result_string, js_result_utf8, js_result_size);
155 g_string_assign(result, js_result_utf8);
158 JSStringRelease(js_result_string);
160 else if (js_exc) {
161 size_t size;
162 JSStringRef prop, val;
163 JSObjectRef exc = JSValueToObject(context, js_exc, NULL);
165 printf("Exception occured while executing script:\n");
167 /* Print file */
168 prop = JSStringCreateWithUTF8CString("sourceURL");
169 val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL);
170 size = JSStringGetMaximumUTF8CStringSize(val);
171 if(size) {
172 char cstr[size];
173 JSStringGetUTF8CString(val, cstr, size);
174 printf("At %s", cstr);
176 JSStringRelease(prop);
177 JSStringRelease(val);
179 /* Print line */
180 prop = JSStringCreateWithUTF8CString("line");
181 val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL);
182 size = JSStringGetMaximumUTF8CStringSize(val);
183 if(size) {
184 char cstr[size];
185 JSStringGetUTF8CString(val, cstr, size);
186 printf(":%s: ", cstr);
188 JSStringRelease(prop);
189 JSStringRelease(val);
191 /* Print message */
192 val = JSValueToStringCopy(context, exc, NULL);
193 size = JSStringGetMaximumUTF8CStringSize(val);
194 if(size) {
195 char cstr[size];
196 JSStringGetUTF8CString(val, cstr, size);
197 printf("%s\n", cstr);
199 JSStringRelease(val);
202 /* cleanup */
203 JSStringRelease(js_script);
204 JSStringRelease(js_file);
206 return g_string_free(result, FALSE);
209 static gint
210 luaH_webview_eval_js(lua_State *L)
212 widget_t *w = luaH_checkudata(L, 1, &widget_class);
213 WebKitWebView *view = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(w->widget), "webview"));
214 const gchar *script = luaL_checkstring(L, 2);
215 const gchar *filename = luaL_checkstring(L, 3);
217 /* evaluate javascript script and push return result onto lua stack */
218 const gchar *result = webview_eval_js(view, script, filename);
219 lua_pushstring(L, result);
220 return 1;
223 static void
224 notify_progress_cb(WebKitWebView *v, GParamSpec *s, widget_t *w)
226 (void) v;
227 (void) s;
229 lua_State *L = globalconf.L;
230 luaH_object_push(L, w->ref);
231 luaH_object_emit_signal(L, -1, "property::progress", 0, 0);
232 lua_pop(L, 1);
235 static void
236 title_changed_cb(WebKitWebView *v, WebKitWebFrame *f, const gchar *title, widget_t *w)
238 (void) f;
239 (void) v;
240 (void) title;
242 lua_State *L = globalconf.L;
243 luaH_object_push(L, w->ref);
244 luaH_object_emit_signal(L, -1, "title-changed", 0, 0);
245 lua_pop(L, 1);
248 inline static void
249 update_uri(GtkWidget *view, const gchar *uri, widget_t *w)
251 /* return if uri has not changed */
252 if (!g_strcmp0(uri, g_object_get_data(G_OBJECT(view), "uri")))
253 return;
255 g_object_set_data_full(G_OBJECT(view), "uri", g_strdup(uri), g_free);
256 lua_State *L = globalconf.L;
257 luaH_object_push(L, w->ref);
258 luaH_object_emit_signal(L, -1, "property::uri", 0, 0);
259 lua_pop(L, 1);
262 static void
263 notify_load_status_cb(WebKitWebView *v, GParamSpec *s, widget_t *w)
265 (void) s;
267 update_uri(GTK_WIDGET(v), webkit_web_view_get_uri(v), w);
269 /* Get load status */
270 WebKitLoadStatus status;
271 g_object_get(G_OBJECT(v), "load-status", &status, NULL);
273 lua_State *L = globalconf.L;
274 luaH_object_push(L, w->ref);
276 /* get status literal */
277 gboolean found = FALSE;
278 for (guint i = 0; i < LENGTH(load_status_names); i++) {
279 if (load_status_names[i].atom != status) continue;
280 lua_pushstring(L, load_status_names[i].name);
281 found = TRUE;
282 break;
284 if (!found)
285 lua_pushstring(L, "unknown");
287 luaH_object_emit_signal(L, -2, "load-status", 1, 0);
288 lua_pop(L, 1);
291 static gboolean
292 mime_type_decision_cb(WebKitWebView *v, WebKitWebFrame *f,
293 WebKitNetworkRequest *r, gchar *mime, WebKitWebPolicyDecision *pd,
294 widget_t *w)
296 (void) v;
297 (void) f;
298 lua_State *L = globalconf.L;
299 const gchar *uri = webkit_network_request_get_uri(r);
300 gint ret;
302 luaH_object_push(L, w->ref);
303 lua_pushstring(L, uri);
304 lua_pushstring(L, mime);
305 ret = luaH_object_emit_signal(L, -3, "mime-type-decision", 2, 1);
307 if (ret && !luaH_checkboolean(L, -1))
308 /* User responded with false, ignore request */
309 webkit_web_policy_decision_ignore(pd);
310 else if (!webkit_web_view_can_show_mime_type(v, mime))
311 webkit_web_policy_decision_download(pd);
312 else
313 webkit_web_policy_decision_use(pd);
315 lua_pop(L, ret + 1);
316 return TRUE;
319 static gboolean
320 download_request_cb(WebKitWebView *v, GObject *dl, widget_t *w)
322 (void) v;
323 const gchar *uri = webkit_download_get_uri((WebKitDownload *) dl);
324 const gchar *filename = webkit_download_get_suggested_filename((WebKitDownload *) dl);
326 lua_State *L = globalconf.L;
327 luaH_object_push(L, w->ref);
328 lua_pushstring(L, uri);
329 lua_pushstring(L, filename);
330 luaH_object_emit_signal(L, -3, "download-request", 2, 0);
331 lua_pop(L, 1);
333 return FALSE;
336 static void
337 link_hover_cb(WebKitWebView *view, const char *t, const gchar *link, widget_t *w)
339 (void) t;
340 lua_State *L = globalconf.L;
341 GObject *ws = G_OBJECT(view);
342 gchar *last_hover = g_object_get_data(ws, "hovered-uri");
344 /* links are identical, do nothing */
345 if (last_hover && !g_strcmp0(last_hover, link))
346 return;
348 luaH_object_push(L, w->ref);
350 if (last_hover) {
351 lua_pushstring(L, last_hover);
352 g_object_set_data(ws, "hovered-uri", NULL);
353 luaH_object_emit_signal(L, -2, "link-unhover", 1, 0);
356 if (link) {
357 lua_pushstring(L, link);
358 g_object_set_data_full(ws, "hovered-uri", g_strdup(link), g_free);
359 luaH_object_emit_signal(L, -2, "link-hover", 1, 0);
362 luaH_object_emit_signal(L, -1, "property::hovered_uri", 0, 0);
363 lua_pop(L, 1);
366 /* Raises the "navigation-request" signal on a webkit navigation policy
367 * decision request. The default action is to load the requested uri.
369 * The signal handler is able to:
370 * - return true for the handler execution to stop and the request to continue
371 * - return false for the handler execution to stop and the request to hault
372 * - do nothing and give the navigation decision to the next signal handler
374 * This signal is also where you would attach custom scheme handlers to take
375 * over the navigation request by launching an external application.
377 static gboolean
378 navigation_decision_cb(WebKitWebView *v, WebKitWebFrame *f,
379 WebKitNetworkRequest *r, WebKitWebNavigationAction *a,
380 WebKitWebPolicyDecision *p, widget_t *w)
382 (void) v;
383 (void) f;
384 (void) a;
386 lua_State *L = globalconf.L;
387 const gchar *uri = webkit_network_request_get_uri(r);
388 gint ret;
390 luaH_object_push(L, w->ref);
391 lua_pushstring(L, uri);
392 ret = luaH_object_emit_signal(L, -2, "navigation-request", 1, 1);
394 if (ret && !luaH_checkboolean(L, -1))
395 /* User responded with false, do not continue navigation request */
396 webkit_web_policy_decision_ignore(p);
397 else
398 webkit_web_policy_decision_use(p);
400 lua_pop(L, ret + 1);
401 return TRUE;
404 inline static gint
405 push_adjustment_values(lua_State *L, GtkAdjustment *adjustment)
407 gdouble view_size = gtk_adjustment_get_page_size(adjustment);
408 gdouble value = gtk_adjustment_get_value(adjustment);
409 gdouble max = gtk_adjustment_get_upper(adjustment) - view_size;
410 lua_pushnumber(L, value);
411 lua_pushnumber(L, (max < 0 ? 0 : max));
412 lua_pushnumber(L, view_size);
413 return 3;
416 static gint
417 luaH_webview_get_vscroll(lua_State *L)
419 widget_t *w = luaH_checkudata(L, 1, &widget_class);
420 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget));
421 return push_adjustment_values(L, adjustment);
424 static gint
425 luaH_webview_get_hscroll(lua_State *L)
427 widget_t *w = luaH_checkudata(L, 1, &widget_class);
428 GtkAdjustment *adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget));
429 return push_adjustment_values(L, adjustment);
432 inline static void
433 set_adjustment(GtkAdjustment *adjustment, gdouble new)
435 gdouble view_size = gtk_adjustment_get_page_size(adjustment);
436 gdouble max = gtk_adjustment_get_upper(adjustment) - view_size;
437 gtk_adjustment_set_value(adjustment, ((new < 0 ? 0 : new) > max ? max : new));
440 static gint
441 luaH_webview_set_scroll_vert(lua_State *L)
443 widget_t *w = luaH_checkudata(L, 1, &widget_class);
444 gdouble value = (gdouble) luaL_checknumber(L, 2);
445 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(w->widget));
446 set_adjustment(adjustment, value);
447 return 0;
450 static gint
451 luaH_webview_set_scroll_horiz(lua_State *L)
453 widget_t *w = luaH_checkudata(L, 1, &widget_class);
454 gdouble value = (gdouble) luaL_checknumber(L, 2);
455 GtkAdjustment *adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(w->widget));
456 set_adjustment(adjustment, value);
457 return 0;
460 static gint
461 luaH_webview_go_back(lua_State *L)
463 widget_t *w = luaH_checkudata(L, 1, &widget_class);
464 gint steps = (gint) luaL_checknumber(L, 2);
465 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
466 webkit_web_view_go_back_or_forward(WEBKIT_WEB_VIEW(view), steps * -1);
467 return 0;
470 static gint
471 luaH_webview_go_forward(lua_State *L)
473 widget_t *w = luaH_checkudata(L, 1, &widget_class);
474 gint steps = (gint) luaL_checknumber(L, 2);
475 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
476 webkit_web_view_go_back_or_forward(WEBKIT_WEB_VIEW(view), steps);
477 return 0;
480 static gint
481 luaH_webview_search(lua_State *L)
483 widget_t *w = luaH_checkudata(L, 1, &widget_class);
484 WebKitWebView *view = WEBKIT_WEB_VIEW(GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview")));
485 const gchar *text = luaL_checkstring(L, 2);
486 gboolean case_sensitive = luaH_checkboolean(L, 3);
487 gboolean forward = luaH_checkboolean(L, 4);
488 gboolean wrap = luaH_checkboolean(L, 5);
490 webkit_web_view_unmark_text_matches(view);
491 webkit_web_view_search_text(view, text, case_sensitive, forward, wrap);
492 webkit_web_view_mark_text_matches(view, text, case_sensitive, 0);
493 webkit_web_view_set_highlight_text_matches(view, TRUE);
494 return 0;
497 static gint
498 luaH_webview_clear_search(lua_State *L)
500 widget_t *w = luaH_checkudata(L, 1, &widget_class);
501 WebKitWebView *view = WEBKIT_WEB_VIEW(GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview")));
502 webkit_web_view_unmark_text_matches(view);
503 return 0;
506 inline static GObject*
507 get_settings_object(GtkWidget *view, property_value_scope scope)
509 switch (scope) {
510 case SETTINGS:
511 return G_OBJECT(webkit_web_view_get_settings(WEBKIT_WEB_VIEW(view)));
512 case WEBKITVIEW:
513 return G_OBJECT(view);
514 case SOUPSESSION:
515 return G_OBJECT(Soup.session);
516 default:
517 break;
519 return NULL;
522 static gint
523 luaH_webview_get_prop(lua_State *L)
525 widget_t *w = luaH_checkudata(L, 1, &widget_class);
526 const gchar *prop = luaL_checkstring(L, 2);
527 GtkWidget *view = GTK_WIDGET(g_object_get_data(G_OBJECT(w->widget), "webview"));
528 GObject *ws;
529 property_tmp_values tmp;
530 SoupURI *u;
532 for (guint i = 0; i < LENGTH(properties); i++) {
533 if (g_strcmp0(properties[i].name, prop))
534 continue;
536 ws = get_settings_object(view, properties[i].scope);
538 switch(properties[i].type) {
539 case BOOL:
540 g_object_get(ws, prop, &tmp.b, NULL);
541 lua_pushboolean(L, tmp.b);
542 return 1;
544 case CHAR:
545 g_object_get(ws, prop, &tmp.c, NULL);
546 lua_pushstring(L, tmp.c);
547 g_free(tmp.c);
548 return 1;
550 case INT:
551 g_object_get(ws, prop, &tmp.i, NULL);
552 lua_pushnumber(L, tmp.i);
553 return 1;
555 case FLOAT:
556 g_object_get(ws, prop, &tmp.f, NULL);
557 lua_pushnumber(L, tmp.f);
558 return 1;
560 case DOUBLE:
561 g_object_get(ws, prop, &tmp.d, NULL);
562 lua_pushnumber(L, tmp.d);
563 return 1;
565 case URI:
566 g_object_get(ws, prop, &u, NULL);
567 tmp.c = soup_uri_to_string(u, 0);
568 lua_pushstring(L, tmp.c);
569 soup_uri_free(u);
570 g_free(tmp.c);
571 return 1;
573 default:
574 warn("unknown property type for: %s", properties[i].name);
575 break;
578 warn("unknown property: %s", prop);
579 return 0;
582 static gint
583 luaH_webview_set_prop(lua_State *L)
585 size_t len;
586 widget_t *w = luaH_checkudata(L, 1, &widget_class);
587 const gchar *prop = luaL_checklstring(L, 2, &len);
588 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
589 GObject *ws;
590 property_tmp_values tmp;
591 SoupURI *u;
593 for (guint i = 0; i < LENGTH(properties); i++) {
594 if (g_strcmp0(properties[i].name, prop))
595 continue;
597 if (!properties[i].writable) {
598 warn("attempt to set read-only property: %s", prop);
599 return 0;
602 ws = get_settings_object(view, properties[i].scope);
604 switch(properties[i].type) {
605 case BOOL:
606 tmp.b = luaH_checkboolean(L, 3);
607 g_object_set(ws, prop, tmp.b, NULL);
608 return 0;
610 case CHAR:
611 tmp.c = (gchar*) luaL_checklstring(L, 3, &len);
612 g_object_set(ws, prop, tmp.c, NULL);
613 return 0;
615 case INT:
616 tmp.i = (gint) luaL_checknumber(L, 3);
617 g_object_set(ws, prop, tmp.i, NULL);
618 return 0;
620 case FLOAT:
621 tmp.f = (gfloat) luaL_checknumber(L, 3);
622 g_object_set(ws, prop, tmp.f, NULL);
623 return 0;
625 case DOUBLE:
626 tmp.d = (gdouble) luaL_checknumber(L, 3);
627 g_object_set(ws, prop, tmp.d, NULL);
628 return 0;
630 case URI:
631 tmp.c = (gchar*) luaL_checkstring(L, 3);
632 u = soup_uri_new(tmp.c);
633 if (SOUP_URI_VALID_FOR_HTTP(u))
634 g_object_set(ws, prop, u, NULL);
635 else
636 luaL_error(L, "cannot parse uri: %s", tmp.c);
637 soup_uri_free(u);
638 return 0;
640 default:
641 warn("unknown property type for: %s", properties[i].name);
642 break;
645 warn("unknown property: %s", prop);
646 return 0;
649 static gint
650 luaH_webview_loading(lua_State *L)
652 widget_t *w = luaH_checkudata(L, 1, &widget_class);
653 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
654 WebKitLoadStatus s;
655 g_object_get(G_OBJECT(view), "load-status", &s, NULL);
656 switch (s) {
657 case WEBKIT_LOAD_PROVISIONAL:
658 case WEBKIT_LOAD_COMMITTED:
659 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
660 lua_pushboolean(L, TRUE);
661 break;
663 default:
664 lua_pushboolean(L, FALSE);
665 break;
667 return 1;
670 void
671 show_scrollbars(widget_t *w, gboolean show)
673 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
674 WebKitWebFrame *mf = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(view));
675 gulong id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(view), "hide_handler_id"));
677 if (show) {
678 if (id)
679 g_signal_handler_disconnect((gpointer) mf, id);
680 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
681 id = 0;
682 } else if (!id) {
683 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w->widget), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
684 id = g_signal_connect(G_OBJECT(mf), "scrollbars-policy-changed", G_CALLBACK(true_cb), NULL);
686 g_object_set_data(G_OBJECT(view), "hide_handler_id", GINT_TO_POINTER(id));
689 static gint
690 luaH_webview_index(lua_State *L, luakit_token_t token)
692 widget_t *w = luaH_checkudata(L, 1, &widget_class);
693 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
695 switch(token)
697 case L_TK_DESTROY:
698 lua_pushcfunction(L, luaH_widget_destroy);
699 return 1;
701 case L_TK_GET_SCROLL_VERT:
702 lua_pushcfunction(L, luaH_webview_get_vscroll);
703 return 1;
705 case L_TK_GET_SCROLL_HORIZ:
706 lua_pushcfunction(L, luaH_webview_get_hscroll);
707 return 1;
709 case L_TK_SET_SCROLL_VERT:
710 lua_pushcfunction(L, luaH_webview_set_scroll_vert);
711 return 1;
713 case L_TK_SET_SCROLL_HORIZ:
714 lua_pushcfunction(L, luaH_webview_set_scroll_horiz);
715 return 1;
717 case L_TK_EVAL_JS:
718 lua_pushcfunction(L, luaH_webview_eval_js);
719 return 1;
721 case L_TK_SEARCH:
722 lua_pushcfunction(L, luaH_webview_search);
723 return 1;
725 case L_TK_CLEAR_SEARCH:
726 lua_pushcfunction(L, luaH_webview_clear_search);
727 return 1;
729 case L_TK_SET_PROP:
730 lua_pushcfunction(L, luaH_webview_set_prop);
731 return 1;
733 case L_TK_GET_PROP:
734 lua_pushcfunction(L, luaH_webview_get_prop);
735 return 1;
737 case L_TK_HOVERED_URI:
738 lua_pushstring(L, g_object_get_data(G_OBJECT(view), "hovered-uri"));
739 return 1;
741 case L_TK_URI:
742 lua_pushstring(L, g_object_get_data(G_OBJECT(view), "uri"));
743 return 1;
745 case L_TK_SHOW:
746 lua_pushcfunction(L, luaH_widget_show);
747 return 1;
749 case L_TK_HIDE:
750 lua_pushcfunction(L, luaH_widget_hide);
751 return 1;
753 case L_TK_FOCUS:
754 lua_pushcfunction(L, luaH_widget_focus);
755 return 1;
757 case L_TK_LOADING:
758 lua_pushcfunction(L, luaH_webview_loading);
759 return 1;
761 case L_TK_GO_BACK:
762 lua_pushcfunction(L, luaH_webview_go_back);
763 return 1;
765 case L_TK_GO_FORWARD:
766 lua_pushcfunction(L, luaH_webview_go_forward);
767 return 1;
769 default:
770 warn("unknown property: %s", luaL_checkstring(L, 2));
771 break;
774 return 0;
777 /* The __newindex method for the webview object */
778 static gint
779 luaH_webview_newindex(lua_State *L, luakit_token_t token)
781 size_t len;
782 widget_t *w = luaH_checkudata(L, 1, &widget_class);
783 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
784 gchar *uri;
786 switch(token)
788 case L_TK_URI:
789 uri = (gchar*) luaL_checklstring(L, 3, &len);
790 if (g_strrstr(uri, "://") || !g_strcmp0(uri, "about:blank"))
791 uri = g_strdup(uri);
792 else
793 uri = g_strdup_printf("http://%s", uri);
794 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(view), uri);
795 g_object_set_data_full(G_OBJECT(view), "uri", uri, g_free);
796 break;
798 case L_TK_SHOW_SCROLLBARS:
799 show_scrollbars(w, luaH_checkboolean(L, 3));
800 return 0;
802 default:
803 warn("unknown property: %s", luaL_checkstring(L, 2));
804 return 0;
807 return luaH_object_emit_property_signal(L, 1);
810 static gboolean
811 expose_cb(GtkWidget *widget, GdkEventExpose *e, widget_t *w)
813 (void) e;
814 (void) widget;
815 lua_State *L = globalconf.L;
816 luaH_object_push(L, w->ref);
817 luaH_object_emit_signal(L, -1, "expose", 0, 0);
818 lua_pop(L, 1);
819 return FALSE;
822 static gboolean
823 wv_button_press_cb(GtkWidget *view, GdkEventButton *event, widget_t *w)
825 if((event->type != GDK_BUTTON_PRESS) || (event->button != 1))
826 return FALSE;
828 /* get webview hit context */
829 WebKitHitTestResult *ht = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(view), event);
830 guint c;
831 g_object_get(ht, "context", &c, NULL);
832 gint context = (gint) c;
834 lua_State *L = globalconf.L;
835 luaH_object_push(L, w->ref);
836 /* raise "form-active" when a user clicks on a form field and raise
837 * "root-active" when a user clicks elsewhere */
838 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
839 luaH_object_emit_signal(L, -1, "form-active", 0, 0);
840 else if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)
841 luaH_object_emit_signal(L, -1, "root-active", 0, 0);
842 lua_pop(L, 1);
843 return FALSE;
846 static void
847 webview_destructor(widget_t *w)
849 GtkWidget *view = g_object_get_data(G_OBJECT(w->widget), "webview");
850 gtk_widget_destroy(GTK_WIDGET(view));
851 gtk_widget_destroy(GTK_WIDGET(w->widget));
854 widget_t *
855 widget_webview(widget_t *w)
857 w->index = luaH_webview_index;
858 w->newindex = luaH_webview_newindex;
859 w->destructor = webview_destructor;
861 /* init soup session & cookies handling */
862 if (!Soup.session) {
863 Soup.session = webkit_get_default_session();
864 gchar *cookie_file = g_build_filename(globalconf.data_dir, "cookies.txt", NULL);
865 Soup.cookiejar = soup_cookie_jar_text_new(cookie_file, FALSE);
866 soup_session_add_feature(Soup.session, (SoupSessionFeature*) Soup.cookiejar);
867 g_free(cookie_file);
870 GtkWidget *view = webkit_web_view_new();
871 w->widget = gtk_scrolled_window_new(NULL, NULL);
872 g_object_set_data(G_OBJECT(w->widget), "widget", w);
873 g_object_set_data(G_OBJECT(w->widget), "webview", view);
874 gtk_container_add(GTK_CONTAINER(w->widget), view);
876 show_scrollbars(w, TRUE);
878 /* connect webview signals */
879 g_object_connect((GObject*)view,
880 "signal::button-press-event", (GCallback)wv_button_press_cb, w,
881 "signal::download-requested", (GCallback)download_request_cb, w,
882 "signal::expose-event", (GCallback)expose_cb, w,
883 "signal::focus-in-event", (GCallback)focus_cb, w,
884 "signal::focus-out-event", (GCallback)focus_cb, w,
885 "signal::hovering-over-link", (GCallback)link_hover_cb, w,
886 "signal::key-press-event", (GCallback)key_press_cb, w,
887 "signal::mime-type-policy-decision-requested", (GCallback)mime_type_decision_cb, w,
888 "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb, w,
889 "signal::notify::load-status", (GCallback)notify_load_status_cb, w,
890 "signal::notify::progress", (GCallback)notify_progress_cb, w,
891 "signal::parent-set", (GCallback)parent_set_cb, w,
892 "signal::title-changed", (GCallback)title_changed_cb, w,
893 NULL);
895 /* setup */
896 gtk_widget_show(view);
897 gtk_widget_show(w->widget);
899 return w;
902 // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80