#include "config.h" #include #include #include #include #include #include #include #include "web.h" #include "web_history.h" #include "web_utils.h" #include "web_request.h" #include "web_gtkhtml2.h" #include "web_gtkhtml2_js.h" static void web_gtkhtml2_init (gpointer *backend_data, WebPages *pages) { *backend_data = g_new0 (WebGtkhtml2Data, 1); ((WebGtkhtml2Data*)*backend_data)->pages = pages; } void web_gtkhtml2_dom_foreach (DomNode *node, GFunc function, gpointer user_data) { int i; DomNodeList *children; function (node, user_data); if (!dom_Node_hasChildNodes (node)) return; children = dom_Node__get_childNodes (node); for (i = 0; i < dom_NodeList__get_length (children); i++) { DomNode *child = dom_NodeList__get_item (children, i); web_gtkhtml2_dom_foreach (child, function, user_data); } } DomNode * web_gtkhtml2_dom_find (DomNode *node, GEqualFunc function, gpointer user_data) { int i; DomNodeList *children; if (function (node, user_data)) return node; if (!dom_Node_hasChildNodes (node)) return; children = dom_Node__get_childNodes (node); for (i = 0; i < dom_NodeList__get_length (children); i++) { DomNode *child = dom_NodeList__get_item (children, i); DomNode *return_node; return_node = web_gtkhtml2_dom_find ( child, function, user_data); if (return_node) return return_node; } return NULL; } void web_gtkhtml2_print_dom (xmlNodePtr node, int depth) { int i; xmlNodePtr child; if (depth == 0) g_debug ("--- Printing nodes ---"); for (i = 0; i < depth; i++) g_print (" "); g_debug ("Node: %s (%s)", node->name, node->content); for (child = node->children; child; child = child->next) web_gtkhtml2_print_dom (child, depth + 1); } static void web_gtkhtml2_deinit (gpointer backend_data) { g_free (backend_data); } static void web_gtkhtml2_stream_cancel_cb (HtmlStream *stream, gpointer user_data, gpointer cancel_data) { WebRequest *request = cancel_data; if (!request) g_warning ("Stream cancelled after being closed"); else web_request_remove (request); } static void web_gtkhtml2_request_url_cb (HtmlDocument *document, const gchar *url, HtmlStream *stream, WebData *data) { WebRequest *request = g_new0 (WebRequest, 1); request->parent = data; request->url = web_get_abs_url (request->parent->base_url, url); request->data = stream; request->type = WEB_DATA; html_stream_set_cancel_func (stream, web_gtkhtml2_stream_cancel_cb, request); web_request_perform (request); } static void web_gtkhtml2_set_base_cb (HtmlDocument *document, const gchar *url, WebData *data) { /* Note: Used for tag */ /* g_debug ("Set base URL: %s", url);*/ if (data->base_url) g_free (data->base_url); data->base_url = g_strdup (url); } typedef struct { gchar *url; WebData *data; } WebGtkhtml2SubmitData; static gboolean web_gtkhtml2_submit_idle (gpointer data) { WebGtkhtml2SubmitData *wdata = data; web_go (wdata->data, wdata->url, FALSE); g_free (wdata->url); g_free (wdata); return FALSE; } static void web_gtkhtml2_submit_cb (HtmlDocument *document, const gchar *method, const gchar *url, const gchar *encoding, WebData *data) { g_debug ("Method: %s\nUrl: %s\nEncoding: %s", method, url, encoding); if ((!url) || (g_ascii_strcasecmp (url, "GET") == 0)) { WebGtkhtml2SubmitData *wdata = g_new (WebGtkhtml2SubmitData, 1); wdata->url = g_strdup_printf ("%s?%s", method, encoding); wdata->data = data; g_idle_add (web_gtkhtml2_submit_idle, wdata); } } static void web_gtkhtml2_title_changed_cb (HtmlDocument *document, const gchar *new_title, WebData *data) { web_title_change (data, new_title); } static void web_gtkhtml2_link_clicked_cb (HtmlDocument *document, const gchar *url, WebData *data) { web_go (data, url, FALSE); } static gboolean web_gtkhtml2_motion_tooltip_cb (GtkWidget *widget, GdkEventMotion *event, GtkWidget *popup) { g_signal_handlers_disconnect_by_func (widget, web_gtkhtml2_motion_tooltip_cb, popup); gtk_widget_destroy (popup); return FALSE; } static gboolean web_gtkhtml2_title_tooltip_cb (gpointer user_data) { WebGtkhtml2PageData *data = user_data; if (!data->document->hover_node) return FALSE; if (dom_Node_hasAttributes (data->document->hover_node)) { int i; DomNamedNodeMap *map = dom_Node__get_attributes (data->document->hover_node); DomNode *node; for (i = 0, node = dom_NamedNodeMap__get_item (map, i); i < dom_NamedNodeMap__get_length (map); i++, node = dom_NamedNodeMap__get_item (map, i)) { if (strcasecmp ("title", dom_Attr__get_name (node)) == 0) { GtkWidget *popup = gtk_window_new (GTK_WINDOW_POPUP); GtkWidget *label = gtk_label_new ((const gchar *) dom_Attr__get_value (node)); gint x, y; gtk_window_set_position (GTK_WINDOW (popup), GTK_WIN_POS_MOUSE); gtk_container_add (GTK_CONTAINER (popup), label); gtk_widget_show_all (popup); g_signal_connect (G_OBJECT (data->view), "motion-notify-event", G_CALLBACK ( web_gtkhtml2_motion_tooltip_cb), popup); break; } } } return FALSE; } static gboolean web_gtkhtml2_dom_mouse_over_cb (HtmlDocument *document, DomEvent *event, WebGtkhtml2PageData *data) { g_timeout_add (1000, web_gtkhtml2_title_tooltip_cb, data); return FALSE; } static gboolean web_gtkhtml2_dom_mouse_out_cb (HtmlDocument *document, DomEvent *event, WebGtkhtml2PageData *data) { g_source_remove_by_user_data (data); return FALSE; } static void web_gtkhtml2_node_finished_cb (HtmlDocument *doc, DomNode *node, WebGtkhtml2PageData *data) { /* g_debug ("(Node finished: %s (%s))", node->xmlnode->name, node->xmlnode->content);*/ if (strcmp (node->xmlnode->name, "script") == 0) web_gtkhtml2_js_run (data->parent, data, node); } static GtkWidget * web_gtkhtml2_new (gpointer backend_data_p, gpointer *page_data_pointer, WebData *data) { GtkWidget *scroll; WebGtkhtml2PageData *page_data; WebGtkhtml2Data *backend_data = backend_data_p; *page_data_pointer = g_new0 (WebGtkhtml2PageData, 1); page_data = *page_data_pointer; page_data->parent = backend_data; page_data->document = html_document_new (); page_data->view = HTML_VIEW (html_view_new ()); page_data->data = data; html_view_set_document (page_data->view, page_data->document); /* Note: Need to set a blank page, or setting page magnification won't * work... Could this be considered a bug in gtkhtml2? */ if (html_document_open_stream (page_data->document, "text/html")) { html_document_write_stream (page_data->document, BLANK_PAGE, strlen (BLANK_PAGE)); html_document_close_stream (page_data->document); } /* Connect signals */ g_signal_connect (G_OBJECT (page_data->document), "node_finished", G_CALLBACK (web_gtkhtml2_node_finished_cb), page_data); /* g_signal_connect (G_OBJECT (page_data->document), "dom_mouse_over", G_CALLBACK (web_gtkhtml2_dom_mouse_over_cb), page_data); g_signal_connect (G_OBJECT (page_data->document), "dom_mouse_out", G_CALLBACK (web_gtkhtml2_dom_mouse_out_cb), page_data);*/ g_signal_connect (G_OBJECT (page_data->document), "request_url", G_CALLBACK (web_gtkhtml2_request_url_cb), data); g_signal_connect (G_OBJECT (page_data->document), "set_base", G_CALLBACK (web_gtkhtml2_set_base_cb), data); g_signal_connect (G_OBJECT (page_data->document), "link_clicked", G_CALLBACK (web_gtkhtml2_link_clicked_cb), data); g_signal_connect (G_OBJECT (page_data->document), "title_changed", G_CALLBACK (web_gtkhtml2_title_changed_cb), data); g_signal_connect (G_OBJECT (page_data->document), "submit", G_CALLBACK (web_gtkhtml2_submit_cb), data); gtk_widget_show_all (page_data->view); return page_data->view; } static void web_gtkhtml2_destroy (gpointer backend_data, gpointer page_data) { WebGtkhtml2PageData *data = page_data; g_free (data); } typedef struct { const gchar *style; const gchar *media; CssStylesheet *sheet; } WebGtkhtml2Css; void web_gtkhtml2_write (gpointer backend_data, gpointer page_data, WebRequest *request, gchar *buffer, gint size) { WebGtkhtml2PageData *data = page_data; gchar *c; gboolean first_time = FALSE; switch (request->type) { case WEB_LINK : if (!request->data) { first_time = !data->document->dom_document; html_document_open_stream ( data->document, "text/html"); request->data = data->document; if (first_time) { GSList *l; for (l = data->sheets; l; l = l->next) { WebGtkhtml2Css *css = l->data; html_document_add_stylesheet ( data->document, css->sheet); } data->page_request = web_request_copy (request); } } /* Break stream at '>' for JavaScript */ while ((c = g_utf8_strchr (buffer, size, '>'))) { gboolean delay_write = FALSE; c++; if (!data->stalling) { /* Make sure injected html comes before * buffered html */ if (data->inject) delay_write = TRUE; else html_document_write_stream ( data->document, buffer, c - buffer); } else { /* g_debug ("Buffering (%d)...", data->buffer_size);*/ data->buffer = g_realloc ( data->buffer, data->buffer_size + (c - buffer)); memcpy ((data->buffer + data->buffer_size), buffer, c - buffer); data->buffer_size += (c - buffer); } if (!delay_write) { size -= c - buffer; buffer = c; } if (data->inject) { html_document_write_stream ( data->document, data->inject, strlen (data->inject)); g_free (data->inject); data->inject = NULL; } if (first_time) { /* Init JS after dom_document is * created. */ web_gtkhtml2_js_init ( backend_data, page_data); first_time = FALSE; } } if (size > 0) { if (!data->stalling) html_document_write_stream ( data->document, buffer, size); else { /* g_debug ("Buffering...");*/ data->buffer = g_realloc ( data->buffer, data->buffer_size + size); memcpy (data->buffer + data->buffer_size, buffer, size); data->buffer_size += size; } } if (data->stalling_close && !data->stalling) { data->stalling_close = FALSE; g_debug ("Closing after buffer"); web_gtkhtml2_close (backend_data, page_data, request); } break; case WEB_DATA : html_stream_write ((HtmlStream *)request->data, buffer, size); break; } } static void web_gtkhtml2_clear (gpointer backend_data, gpointer page_data) { WebGtkhtml2PageData *data = page_data; GList *r; GSList *s; for (r = data->ext_js; r; r = r->next) web_request_close ((WebRequest *)r->data); g_list_free (data->ext_js); data->ext_js = NULL; /* Note: Doing html_document_clear destroys the stylesheets, so * remove them first so as not to have to re-parse them */ for (s = data->sheets; s; s = s->next) { WebGtkhtml2Css *css = s->data; if (css->sheet) html_document_remove_stylesheet ( data->document, css->sheet); } web_gtkhtml2_js_clear (backend_data, page_data); html_document_clear (data->document); } void web_gtkhtml2_close (gpointer backend_data, gpointer page_data, WebRequest *request) { WebGtkhtml2PageData *data = page_data; if (request->data) { switch (request->type) { case WEB_LINK : /* g_debug ("Closing document stream (%s)", request->url);*/ if (!data->stalling) { GSList *s; html_document_close_stream (data->document); web_request_free (data->page_request); web_gtkhtml2_js_init_events (backend_data, page_data); for (s = data->scripts; s; s = s->next) { gchar *script = (gchar *)s->data; web_gtkhtml2_js_parse (backend_data, page_data, script, strlen (script)); } /* web_gtkhtml2_print_dom ( data->document->dom_document, 0);*/ } else { g_debug ("Buffered close"); data->stalling_close = TRUE; return; } break; case WEB_DATA : /* g_debug ("Closing stream (%s)", request->url);*/ html_stream_set_cancel_func ( (HtmlStream *)request->data, web_gtkhtml2_stream_cancel_cb, NULL); html_stream_close ((HtmlStream *)request->data); break; default : g_warning ("Unknown request type (%d, %s)", request->type, request->url); break; } request->data = NULL; } } static gdouble web_gtkhtml2_get_zoom (gpointer backend_data, gpointer page_data) { WebGtkhtml2PageData *data = page_data; return html_view_get_magnification (data->view); } static void web_gtkhtml2_set_zoom (gpointer backend_data, gpointer page_data, gdouble zoom) { WebGtkhtml2PageData *data = page_data; html_view_set_magnification (data->view, zoom); } static gpointer web_gtkhtml2_add_style (gpointer backend_data, gpointer page_data, const gchar *style, const gchar *media) { WebGtkhtml2PageData *data = page_data; /* g_debug ("Adding user CSS");*/ WebGtkhtml2Css *css = g_new0 (WebGtkhtml2Css, 1); css->style = g_strdup (style); css->media = g_strdup (media); css->sheet = css_parser_parse_stylesheet (css->style, strlen (css->style), NULL, css->media); data->sheets = g_slist_prepend ( data->sheets, css); if (data->document->dom_document) html_document_add_stylesheet (data->document, css->sheet); return g_slist_find (data->sheets, css); } static void web_gtkhtml2_remove_style (gpointer backend_data, gpointer page_data, gpointer style) { WebGtkhtml2PageData *data = page_data; GSList *link = style; if (link) { WebGtkhtml2Css *css = link->data; if (css->sheet) { if (data->document->dom_document) html_document_remove_stylesheet ( data->document, css->sheet); css_stylesheet_destroy (css->sheet); } g_free (css); data->sheets = g_slist_delete_link (data->sheets, link); } } static gpointer web_gtkhtml2_add_script (gpointer backend_data, gpointer page_data, gchar *script) { WebGtkhtml2PageData *data = page_data; script = g_strdup (script); data->scripts = g_slist_prepend (data->scripts, script); return g_slist_find (data->scripts, script); } static void web_gtkhtml2_remove_script (gpointer backend_data, gpointer page_data, gpointer script) { WebGtkhtml2PageData *data = page_data; GSList *link = script; if (script) { gchar *script_string = link->data; g_free (script_string); data->scripts = g_slist_delete_link (data->scripts, link); } } static void web_gtkhtml2_set_media (gpointer backend_data, gpointer page_data, const gchar *media) { WebGtkhtml2PageData *data = page_data; html_document_set_media_type (data->document, media); } static WebBackEnd gtkhtml2_backend = { WEB_API_VERSION, "GtkHTML2", web_gtkhtml2_init, web_gtkhtml2_deinit, web_gtkhtml2_new, web_gtkhtml2_destroy, web_gtkhtml2_write, web_gtkhtml2_clear, web_gtkhtml2_close, web_gtkhtml2_get_zoom, web_gtkhtml2_set_zoom, web_gtkhtml2_add_style, web_gtkhtml2_remove_style, web_gtkhtml2_add_script, web_gtkhtml2_remove_script, web_gtkhtml2_set_media }; DECLARE_WEB_BACKEND(gtkhtml2_backend);