From e61743db9c147049f86f0a4acdf60d8bd729f9b4 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Sat, 7 Jan 2012 01:31:37 -0400 Subject: [PATCH] Formhistory 2.0 with GDOM frontend --- extensions/formhistory/formhistory-frontend.h | 63 +++ .../formhistory/formhistory-gdom-frontend.c | 456 ++++++++++++++++++ .../formhistory/formhistory-js-frontend.c | 149 ++++++ extensions/{ => formhistory}/formhistory.c | 359 ++++---------- 4 files changed, 758 insertions(+), 269 deletions(-) create mode 100644 extensions/formhistory/formhistory-frontend.h create mode 100644 extensions/formhistory/formhistory-gdom-frontend.c create mode 100644 extensions/formhistory/formhistory-js-frontend.c rename extensions/{ => formhistory}/formhistory.c (58%) diff --git a/extensions/formhistory/formhistory-frontend.h b/extensions/formhistory/formhistory-frontend.h new file mode 100644 index 00000000..764e89e6 --- /dev/null +++ b/extensions/formhistory/formhistory-frontend.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2009-2012 Alexander Butenko + Copyright (C) 2009-2012 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. +*/ + +#ifndef __FORMHISTORY_FRONTEND_H__ +#define __FORMHISTORY_FRONTEND_H__ +#include +#include + +#include "config.h" +#if HAVE_UNISTD_H + #include +#endif + +#if WEBKIT_CHECK_VERSION (1, 3, 1) + #define FORMHISTORY_USE_GDOM 1 +#else + #define FORMHISTORY_USE_JS 1 +#endif + +typedef struct +{ + sqlite3* db; + #if FORMHISTORY_USE_GDOM + WebKitDOMElement* element; + int completion_timeout; + GtkTreeModel* completion_model; + GtkWidget* treeview; + GtkWidget* popup; + GtkWidget* root; + gchar* oldkeyword; + guint selection_index; + #else + gchar* jsforms; + #endif +} FormHistoryPriv; + +FormHistoryPriv* +formhistory_private_new (); + +void +formhistory_private_destroy (FormHistoryPriv *priv); + +gboolean +formhistory_construct_popup_gui (FormHistoryPriv* priv); + +void +formhistory_setup_suggestions (WebKitWebView* web_view, + JSContextRef js_context, + MidoriExtension* extension); + +void +formhistory_suggestions_hide_cb (WebKitDOMElement* element, + WebKitDOMEvent* dom_event, + FormHistoryPriv* priv); + +#endif diff --git a/extensions/formhistory/formhistory-gdom-frontend.c b/extensions/formhistory/formhistory-gdom-frontend.c new file mode 100644 index 00000000..230e1a94 --- /dev/null +++ b/extensions/formhistory/formhistory-gdom-frontend.c @@ -0,0 +1,456 @@ +/* + Copyright (C) 2009-2012 Alexander Butenko + Copyright (C) 2009-2012 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. +*/ +#include "formhistory-frontend.h" +#ifdef FORMHISTORY_USE_GDOM +#define COMPLETION_DELAY 200 + +FormHistoryPriv* +formhistory_private_new () +{ + FormHistoryPriv* priv; + + priv = g_slice_new (FormHistoryPriv); + priv->oldkeyword = g_strdup (""); + priv->selection_index = -1; + return priv; +} + +void +formhistory_suggestions_hide_cb (WebKitDOMElement* element, + WebKitDOMEvent* dom_event, + FormHistoryPriv* priv) +{ + if (gtk_widget_get_visible (priv->popup)) + gtk_widget_hide (priv->popup); + priv->selection_index = -1; +} + +static void +formhistory_suggestion_set (GtkTreePath* path, + FormHistoryPriv* priv) +{ + GtkTreeIter iter; + gchar* value; + + if (!gtk_tree_model_get_iter (priv->completion_model, &iter, path)) + return; + + gtk_tree_model_get (priv->completion_model, &iter, 0, &value, -1); + g_object_set (priv->element, "value", value, NULL); + g_free (value); +} + +static gboolean +formhistory_suggestion_selected_cb (GtkWidget* treeview, + GdkEventButton* event, + FormHistoryPriv* priv) + +{ + GtkTreePath* path; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), + event->x, event->y, &path, NULL, NULL, NULL)) + { + formhistory_suggestion_set (path, priv); + formhistory_suggestions_hide_cb (NULL, NULL, priv); + gtk_tree_path_free (path); + return TRUE; + } + return FALSE; +} + +static void +get_absolute_offset_for_element (WebKitDOMElement* element, + glong* x, + glong* y, + gboolean mainframe) +{ + WebKitDOMElement* offset_parent; + WebKitDOMDocument* element_document; + gint offset_top = 0, offset_left = 0; + gboolean ismainframe = FALSE; + WebKitDOMNodeList* frames; + WebKitDOMDocument* fdoc; + int i; + + frames = g_object_get_data (G_OBJECT (element), "framelist"); + element_document = g_object_get_data (G_OBJECT (element), "doc"); + g_object_get (element, "offset-left", &offset_left, + "offset-top", &offset_top, + "offset-parent", &offset_parent, + NULL); + *x += offset_left; + *y += offset_top; + + /* To avoid deadlock check only first element of the mainframe parent */ + if (mainframe == TRUE) + return; + + if (offset_parent) + goto finish; + + /* Element havent returned any parents. Thats mean or there is no parents or we are inside the frame + Loop over all frames we have to find frame == element_document which is a root for our element + and get its offsets */ + for (i = 0; i < webkit_dom_node_list_get_length (frames); i++) + { + WebKitDOMNode *frame = webkit_dom_node_list_item (frames, i); + + if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (frame)) + fdoc = webkit_dom_html_iframe_element_get_content_document (WEBKIT_DOM_HTML_IFRAME_ELEMENT (frame)); + else + fdoc = webkit_dom_html_frame_element_get_content_document (WEBKIT_DOM_HTML_FRAME_ELEMENT (frame)); + + if (fdoc == element_document) + { + offset_parent = WEBKIT_DOM_ELEMENT (frame); + ismainframe = TRUE; + /* Add extra 4px to ~cover size of borders */ + *y += 4; + break; + } + } + if (!offset_parent) + return; +finish: + /* Copy set properties to parents as they dont have them set */ + /* FIXME: Seems we need to drop them afterwards to save some memory? */ + g_object_set_data (G_OBJECT (offset_parent), "doc", element_document); + g_object_set_data (G_OBJECT (offset_parent), "framelist", frames); + get_absolute_offset_for_element (offset_parent, x, y, ismainframe); +} + +static void +formhistory_reposition_popup (FormHistoryPriv* priv, + GtkWidget* widget) +{ + GdkWindow* window; + gint rx, ry; + gint wx, wy; + glong x = 0, y = 0; + glong height; + + /* Position of a root window */ + window = gtk_widget_get_window (widget); + gdk_window_get_position (window, &rx, &ry); + + /* Postion of webview in root window */ + window = gtk_widget_get_window (priv->root); + gdk_window_get_position (window, &wx, &wy); + + /* Position of editbox on the webview */ + get_absolute_offset_for_element (priv->element, &x, &y, FALSE); + /* Add height as menu should start under editbox, now on top of it */ + g_object_get (priv->element, "client-height", &height, NULL); + y += height + 1; + + gtk_window_move (GTK_WINDOW (priv->popup), rx + wx + x, ry +wy + y); + gtk_tree_view_columns_autosize (GTK_TREE_VIEW (priv->treeview)); + /* FIXME: Adjust size according to treeview width and some reasonable height */ + gtk_window_resize (GTK_WINDOW (priv->popup), 50, 80); +} + +static void +formhistory_suggestions_show (FormHistoryPriv* priv) +{ + GtkListStore* store; + static sqlite3_stmt* stmt; + const gchar* value; + const gchar* name; + const char* sqlcmd; + gint result; + + g_source_remove (priv->completion_timeout); + + g_object_get (priv->element, + "name", &name, + "value", &value, + NULL); + katze_assign (priv->oldkeyword, g_strdup (value)); + if (!priv->popup) + formhistory_construct_popup_gui (priv); + + if (!stmt) + { + if (!priv->db) + return; + + sqlcmd = "SELECT DISTINCT value FROM forms WHERE field = ?1 and value like ?2"; + sqlite3_prepare_v2 (priv->db, sqlcmd, strlen (sqlcmd) + 1, &stmt, NULL); + } + + gchar* likedvalue = g_strdup_printf ("%s%%", value); + sqlite3_bind_text (stmt, 1, name, -1, NULL); + sqlite3_bind_text (stmt, 2, likedvalue, -1, g_free); + result = sqlite3_step (stmt); + + if (result != SQLITE_ROW) + { + if (result == SQLITE_ERROR) + g_print (_("Failed to select suggestions\n")); + sqlite3_reset (stmt); + sqlite3_clear_bindings (stmt); + formhistory_suggestions_hide_cb (NULL, NULL, priv); + return; + } + + store = GTK_LIST_STORE (priv->completion_model); + gtk_list_store_clear (store); + int pos = 0; + + while (result == SQLITE_ROW) + { + pos++; + const unsigned char* text = sqlite3_column_text (stmt, 0); + gtk_list_store_insert_with_values (store, NULL, pos, 0, text, -1); + result = sqlite3_step (stmt); + } + sqlite3_reset (stmt); + sqlite3_clear_bindings (stmt); + gtk_widget_grab_focus (priv->treeview); + + if (gtk_widget_get_visible (priv->popup)) + return; + + GtkWidget* toplevel = gtk_widget_get_toplevel (GTK_WIDGET (priv->root)); + gtk_window_set_screen (GTK_WINDOW (priv->popup), + gtk_widget_get_screen (GTK_WIDGET (priv->root))); + /* FIXME: If Midori window is small, popup doesn't show up */ + gtk_window_set_transient_for (GTK_WINDOW (priv->popup), GTK_WINDOW (toplevel)); + formhistory_reposition_popup (priv, toplevel); + gtk_widget_show_all (priv->popup); + gtk_widget_grab_focus (priv->treeview); +} + +static void +formhistory_editbox_key_pressed_cb (WebKitDOMElement* element, + WebKitDOMEvent* dom_event, + FormHistoryPriv* priv) +{ + glong key; + GtkTreePath* path; + const gchar* keyword; + + /* FIXME: Priv is still set after module is disabled */ + if (!priv) + return; + + if (priv->completion_timeout > 0) + g_source_remove (priv->completion_timeout); + + g_object_get (element, "value", &keyword, NULL); + priv->element = element; + + key = webkit_dom_ui_event_get_key_code (WEBKIT_DOM_UI_EVENT (dom_event)); + + /* Ignore some control chars */ + if (key < 20 && key != 8) + return; + + gint matches = gtk_tree_model_iter_n_children (priv->completion_model, NULL); + + switch (key) + { + /* ESC key*/ + case 27: + case 35: + case 36: + /* Left key*/ + case 37: + /* Right key*/ + case 39: + if (key == 27) + g_object_set (element, "value", priv->oldkeyword, NULL); + formhistory_suggestions_hide_cb (element, dom_event, priv); + return; + break; + /*FIXME: Del to delete entry */ + /* Up key */ + case 38: + /* Down key */ + case 40: + if (gtk_widget_get_visible (priv->popup)) + { + if (key == 38) + { + if (priv->selection_index == -1) + priv->selection_index = matches - 1; + else + priv->selection_index = MAX (priv->selection_index - 1, 1); + } + else + { + priv->selection_index = MIN (priv->selection_index + 1, matches -1); + } + + path = gtk_tree_path_new_from_indices (priv->selection_index, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->treeview), path, NULL, FALSE); + formhistory_suggestion_set (path, priv); + gtk_tree_path_free (path); + } + else + formhistory_suggestions_show (priv); + return; + break; + /* PgUp, PgDn, Ins */ + case 33: + case 34: + case 45: + break; + } + + if (!(keyword && *keyword && *keyword != ' ')) + { + formhistory_suggestions_hide_cb (element, dom_event, priv); + return; + } + + /* If the same keyword is submitted there's no need to regenerate suggestions */ + if (gtk_widget_get_visible (priv->popup) && + !g_strcmp0 (keyword, priv->oldkeyword)) + return; + priv->completion_timeout = g_timeout_add (COMPLETION_DELAY, + (GSourceFunc)formhistory_suggestions_show, priv); +} + +static void +formhistory_DOMContentLoaded_cb (WebKitDOMElement* window, + WebKitDOMEvent* dom_event, + FormHistoryPriv* priv) +{ + int i; + WebKitDOMDocument* doc; + WebKitDOMNodeList* inputs; + WebKitDOMNodeList* frames; + + if (WEBKIT_DOM_IS_DOCUMENT (window)) + doc = WEBKIT_DOM_DOCUMENT (window); + else + doc = webkit_dom_dom_window_get_document (WEBKIT_DOM_DOM_WINDOW (window)); + inputs = webkit_dom_document_query_selector_all (doc, "input[type='text']", NULL); + frames = g_object_get_data (G_OBJECT (window), "framelist"); + + for (i = 0; i < webkit_dom_node_list_get_length (inputs); i++) + { + const gchar* autocomplete; + WebKitDOMNode* element = webkit_dom_node_list_item (inputs, i); + g_object_get (element, "autocomplete", &autocomplete, NULL); + /* Dont bind if input is not text or autocomplete is disabled */ + if (!g_strcmp0 (autocomplete, "off")) + continue; + + g_object_set_data (G_OBJECT (element), "doc", doc); + g_object_set_data (G_OBJECT (element), "framelist", frames); + /* Add dblclick? */ + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (element), "keyup", + G_CALLBACK (formhistory_editbox_key_pressed_cb), false, + priv); + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (element), "blur", + G_CALLBACK (formhistory_suggestions_hide_cb), false, + priv); + } +} + +void +formhistory_setup_suggestions (WebKitWebView* web_view, + JSContextRef js_context, + MidoriExtension* extension) +{ + WebKitDOMDocument* doc; + WebKitDOMNodeList* frames; + int i; + + FormHistoryPriv* priv = g_object_get_data (G_OBJECT (extension), "priv"); + priv->root = (GtkWidget*)web_view; + doc = webkit_web_view_get_dom_document (web_view); + frames = webkit_dom_document_query_selector_all (doc, "iframe, frame", NULL); + g_object_set_data (G_OBJECT (doc), "framelist", frames); + /* Connect to DOMContentLoaded of the main frame */ + webkit_dom_event_target_add_event_listener( + WEBKIT_DOM_EVENT_TARGET (doc), "DOMContentLoaded", + G_CALLBACK (formhistory_DOMContentLoaded_cb), false, + priv); + + /* Connect to DOMContentLoaded of frames */ + for (i = 0; i < webkit_dom_node_list_get_length (frames); i++) + { + WebKitDOMDOMWindow* framewin; + + WebKitDOMNode* frame = webkit_dom_node_list_item (frames, i); + if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (frame)) + framewin = webkit_dom_html_iframe_element_get_content_window (WEBKIT_DOM_HTML_IFRAME_ELEMENT (frame)); + else + framewin = webkit_dom_html_frame_element_get_content_window (WEBKIT_DOM_HTML_FRAME_ELEMENT (frame)); + g_object_set_data (G_OBJECT (framewin), "framelist", frames); + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (framewin), "DOMContentLoaded", + G_CALLBACK (formhistory_DOMContentLoaded_cb), false, + priv); + } +} + +void +formhistory_private_destroy (FormHistoryPriv *priv) +{ + if (priv->db) + { + sqlite3_close (priv->db); + priv->db = NULL; + } + if (priv->oldkeyword) + g_free (priv->oldkeyword); + gtk_widget_destroy (priv->popup); + priv->popup = NULL; + g_slice_free (FormHistoryPriv, priv); + priv = NULL; +} + +gboolean +formhistory_construct_popup_gui (FormHistoryPriv* priv) +{ + GtkTreeModel* model = NULL; + GtkWidget* popup; + GtkWidget* popup_frame; + GtkWidget* scrolled; + GtkWidget* treeview; + GtkCellRenderer* renderer; + GtkTreeViewColumn* column; + + model = (GtkTreeModel*) gtk_list_store_new (1, G_TYPE_STRING); + priv->completion_model = model; + popup = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_type_hint (GTK_WINDOW (popup), GDK_WINDOW_TYPE_HINT_COMBO); + popup_frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (popup_frame), GTK_SHADOW_ETCHED_IN); + gtk_container_add (GTK_CONTAINER (popup), popup_frame); + scrolled = g_object_new (GTK_TYPE_SCROLLED_WINDOW, + "hscrollbar-policy", GTK_POLICY_NEVER, + "vscrollbar-policy", GTK_POLICY_AUTOMATIC, NULL); + gtk_container_add (GTK_CONTAINER (popup_frame), scrolled); + treeview = gtk_tree_view_new_with_model (model); + priv->treeview = treeview; + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (treeview), TRUE); + gtk_container_add (GTK_CONTAINER (scrolled), treeview); + gtk_widget_set_size_request (gtk_scrolled_window_get_vscrollbar ( + GTK_SCROLLED_WINDOW (scrolled)), -1, 0); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("suggestions", renderer, "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + priv->popup = popup; + + g_signal_connect (treeview, "button-press-event", + G_CALLBACK (formhistory_suggestion_selected_cb), priv); + return TRUE; +} +#endif diff --git a/extensions/formhistory/formhistory-js-frontend.c b/extensions/formhistory/formhistory-js-frontend.c new file mode 100644 index 00000000..1943610a --- /dev/null +++ b/extensions/formhistory/formhistory-js-frontend.c @@ -0,0 +1,149 @@ +/* + Copyright (C) 2009-2012 Alexander Butenko + Copyright (C) 2009-2012 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. +*/ +#include "formhistory-frontend.h" +#ifdef FORMHISTORY_USE_JS + +FormHistoryPriv* +formhistory_private_new () +{ + FormHistoryPriv* priv; + + priv = g_slice_new (FormHistoryPriv); + return priv; +} + +gboolean +formhistory_construct_popup_gui (FormHistoryPriv* priv) +{ + gchar* autosuggest; + gchar* style; + guint i; + gchar* file; + + file = sokoke_find_data_filename ("autosuggestcontrol.js", TRUE); + if (!g_file_get_contents (file, &autosuggest, NULL, NULL)) + { + g_free (file); + return FALSE; + } + g_strchomp (autosuggest); + + katze_assign (file, sokoke_find_data_filename ("autosuggestcontrol.css", TRUE)); + if (!g_file_get_contents (file, &style, NULL, NULL)) + { + g_free (file); + return FALSE; + } + g_strchomp (style); + g_free (file); + + i = 0; + while (style[i]) + { + if (style[i] == '\n') + style[i] = ' '; + i++; + } + + priv->jsforms = g_strdup_printf ( + "%s" + "window.addEventListener ('DOMContentLoaded'," + "function () {" + " if (document.getElementById('formhistory'))" + " return;" + " if (!initSuggestions ())" + " return;" + " var mystyle = document.createElement('style');" + " mystyle.setAttribute('type', 'text/css');" + " mystyle.setAttribute('id', 'formhistory');" + " mystyle.appendChild(document.createTextNode('%s'));" + " var head = document.getElementsByTagName('head')[0];" + " if (head) head.appendChild(mystyle);" + "}, true);", + autosuggest, + style); + g_strstrip (priv->jsforms); + g_free (style); + g_free (autosuggest); + return TRUE; +} + +void +formhistory_setup_suggestions (WebKitWebView* web_view, + JSContextRef js_context, + MidoriExtension* extension) +{ + GString* suggestions; + FormHistoryPriv* priv; + static sqlite3_stmt* stmt; + const char* sqlcmd; + gint result, pos; + + priv = g_object_get_data (G_OBJECT (extension), "priv"); + if (!priv->db) + return; + + if (!stmt) + { + sqlcmd = "SELECT DISTINCT group_concat(value,'\",\"'), field FROM forms \ + GROUP BY field ORDER BY field"; + sqlite3_prepare_v2 (priv->db, sqlcmd, strlen (sqlcmd) + 1, &stmt, NULL); + } + result = sqlite3_step (stmt); + if (result != SQLITE_ROW) + { + if (result == SQLITE_ERROR) + g_print (_("Failed to select suggestions\n")); + sqlite3_reset (stmt); + return; + } + suggestions = g_string_new ( + "function FormSuggestions(eid) { " + "arr = new Array();"); + + while (result == SQLITE_ROW) + { + pos++; + const unsigned char* value = sqlite3_column_text (stmt, 0); + const unsigned char* key = sqlite3_column_text (stmt, 1); + if (value) + { + g_string_append_printf (suggestions, " arr[\"%s\"] = [\"%s\"]; ", + (gchar*)key, (gchar*)value); + } + result = sqlite3_step (stmt); + } + g_string_append (suggestions, "this.suggestions = arr[eid]; }"); + g_string_append (suggestions, priv->jsforms); + sokoke_js_script_eval (js_context, suggestions->str, NULL); + g_string_free (suggestions, TRUE); +} + +void +formhistory_suggestions_hide_cb (WebKitDOMElement* element, + WebKitDOMEvent* dom_event, + FormHistoryPriv* priv) +{ + /* Unused in JS frontend */ + return; +} + +void +formhistory_private_destroy (FormHistoryPriv *priv) +{ + if (priv->db) + { + sqlite3_close (priv->db); + priv->db = NULL; + } + katze_assign (priv->jsforms, NULL); + g_slice_free (FormHistoryPriv, priv); +} +#endif diff --git a/extensions/formhistory.c b/extensions/formhistory/formhistory.c similarity index 58% rename from extensions/formhistory.c rename to extensions/formhistory/formhistory.c index 00e9ea72..b1f71cbe 100644 --- a/extensions/formhistory.c +++ b/extensions/formhistory/formhistory.c @@ -1,124 +1,20 @@ /* - Copyright (C) 2009 Alexander Butenko - Copyright (C) 2009 Christian Dywan + Copyright (C) 2009-2012 Alexander Butenko + Copyright (C) 2009-2012 Christian Dywan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. */ - #define MAXCHARS 60 #define MINCHARS 2 - -#include -#include - -#include "config.h" -#if HAVE_UNISTD_H - #include -#endif - -static GHashTable* global_keys; -static gchar* jsforms; - +#include "formhistory-frontend.h" static void formhistory_toggle_state_cb (GtkAction* action, MidoriBrowser* browser); -static gboolean -formhistory_prepare_js () -{ - gchar* autosuggest; - gchar* style; - guint i; - gchar* file; - - file = sokoke_find_data_filename ("autosuggestcontrol.js", TRUE); - if (!g_file_get_contents (file, &autosuggest, NULL, NULL)) - { - g_free (file); - return FALSE; - } - g_strchomp (autosuggest); - - katze_assign (file, sokoke_find_data_filename ("autosuggestcontrol.css", TRUE)); - if (!g_file_get_contents (file, &style, NULL, NULL)) - { - g_free (file); - return FALSE; - } - g_strchomp (style); - i = 0; - while (style[i]) - { - if (style[i] == '\n') - style[i] = ' '; - i++; - } - - jsforms = g_strdup_printf ( - "%s" - "window.addEventListener ('DOMContentLoaded'," - "function () {" - " if (document.getElementById('formhistory'))" - " return;" - " if (!initSuggestions ())" - " return;" - " var mystyle = document.createElement('style');" - " mystyle.setAttribute('type', 'text/css');" - " mystyle.setAttribute('id', 'formhistory');" - " mystyle.appendChild(document.createTextNode('%s'));" - " var head = document.getElementsByTagName('head')[0];" - " if (head) head.appendChild(mystyle);" - "}, true);", - autosuggest, - style); - g_strstrip (jsforms); - g_free (file); - g_free (style); - g_free (autosuggest); - return TRUE; -} - -static gchar* -formhistory_fixup_value (char* value) -{ - guint i = 0; - g_strchomp (value); - while (value[i]) - { - if (value[i] == '\n') - value[i] = ' '; - else if (value[i] == '"') - value[i] = '\''; - i++; - } - return value; -} - -static gchar* -formhistory_build_js () -{ - GString* suggestions; - GHashTableIter iter; - gpointer key, value; - - suggestions = g_string_new ( - "function FormSuggestions(eid) { " - "arr = new Array();"); - g_hash_table_iter_init (&iter, global_keys); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - g_string_append_printf (suggestions, " arr[\"%s\"] = [%s]; ", - (gchar*)key, (gchar*)value); - } - g_string_append (suggestions, "this.suggestions = arr[eid]; }"); - g_string_append (suggestions, jsforms); - return g_string_free (suggestions, FALSE); -} - static void formhistory_update_database (gpointer db, const gchar* key, @@ -127,6 +23,13 @@ formhistory_update_database (gpointer db, gchar* sqlcmd; gchar* errmsg; gint success; + guint length; + + if (!(value && *value)) + return; + length = strlen (value); + if (length > MAXCHARS || length < MINCHARS) + return; sqlcmd = sqlite3_mprintf ("INSERT INTO forms VALUES" "('%q', '%q', '%q')", @@ -141,48 +44,6 @@ formhistory_update_database (gpointer db, } } -static gboolean -formhistory_update_main_hash (gchar* key, - gchar* value) -{ - guint length; - gchar* tmp; - - if (!(value && *value)) - return FALSE; - length = strlen (value); - if (length > MAXCHARS || length < MINCHARS) - return FALSE; - - formhistory_fixup_value (key); - formhistory_fixup_value (value); - if ((tmp = g_hash_table_lookup (global_keys, (gpointer)key))) - { - gchar* rvalue = g_strdup_printf ("\"%s\"",value); - gchar* patt = g_regex_escape_string (rvalue, -1); - if (!g_regex_match_simple (patt, tmp, - G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY)) - { - gchar* new_value = g_strdup_printf ("%s%s,", tmp, rvalue); - g_hash_table_insert (global_keys, g_strdup (key), new_value); - g_free (rvalue); - g_free (patt); - } - else - { - g_free (rvalue); - g_free (patt); - return FALSE; - } - } - else - { - gchar* new_value = g_strdup_printf ("\"%s\",",value); - g_hash_table_replace (global_keys, g_strdup (key), new_value); - } - return TRUE; -} - static gboolean formhistory_navigation_decision_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, @@ -204,73 +65,60 @@ formhistory_navigation_decision_cb (WebKitWebView* web_view, " var eid = inputs[i].getAttribute('id');" " if (!ename && eid)" " ename=eid;" - " if (inputs[i].getAttribute('autocomplete') != 'off')" - " out += ename+'|,|'+inputs[i].value +'|,|'+inputs[i].type +'|||';" + " out += ename+'|,|'+inputs[i].value +'|,|'+inputs[i].type +'|||';" " }" " }" " return out;" "}" "dumpForm (document.getElementsByTagName('input'))"; - if (webkit_web_navigation_action_get_reason (action) == WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED) + if (webkit_web_navigation_action_get_reason (action) != WEBKIT_WEB_NAVIGATION_REASON_FORM_SUBMITTED) + return FALSE; + + FormHistoryPriv* priv = g_object_get_data (G_OBJECT (extension), "priv"); + JSContextRef js_context = webkit_web_frame_get_global_context (web_frame); + gchar* value = sokoke_js_script_eval (js_context, script, NULL); + + formhistory_suggestions_hide_cb (NULL, NULL, priv); + if (value && *value) { - JSContextRef js_context = webkit_web_frame_get_global_context (web_frame); - gchar* value = sokoke_js_script_eval (js_context, script, NULL); - if (value && *value) + gchar** inputs = g_strsplit (value, "|||", 0); + guint i = 0; + while (inputs[i] != NULL) { - gpointer db = g_object_get_data (G_OBJECT (extension), "formhistory-db"); - gchar** inputs = g_strsplit (value, "|||", 0); - guint i = 0; - while (inputs[i] != NULL) + gchar** parts = g_strsplit (inputs[i], "|,|", 3); + if (parts && parts[0] && parts[1] && parts[2]) { - gchar** parts = g_strsplit (inputs[i], "|,|", 3); - if (parts && parts[0] && parts[1] && parts[2]) - { - /* FIXME: We need to handle passwords */ - if (strcmp (parts[2], "password")) - { - if (formhistory_update_main_hash (parts[0], parts[1])) - formhistory_update_database (db, parts[0], parts[1]); - } - } - g_strfreev (parts); - i++; + /* FIXME: We need to handle passwords */ + if (strcmp (parts[2], "password")) + formhistory_update_database (priv->db, parts[0], parts[1]); } - g_strfreev (inputs); - g_free (value); + g_strfreev (parts); + i++; } + g_strfreev (inputs); + g_free (value); } return FALSE; } static void -formhistory_window_object_cleared_cb (WebKitWebView* web_view, - WebKitWebFrame* web_frame, - JSContextRef js_context, - JSObjectRef js_window) +formhistory_window_object_cleared_cb (WebKitWebView* web_view, + WebKitWebFrame* web_frame, + JSContextRef js_context, + JSObjectRef js_window, + MidoriExtension* extension) { - gchar* script; const gchar* page_uri; page_uri = webkit_web_frame_get_uri (web_frame); - if (!midori_uri_is_http (page_uri)) + if (!page_uri) return; - script = formhistory_build_js (); - sokoke_js_script_eval (js_context, script, NULL); - g_free (script); -} + if (!midori_uri_is_http (page_uri) && !g_str_has_prefix (page_uri, "file")) + return; -static void -formhistory_add_tab_cb (MidoriBrowser* browser, - MidoriView* view, - MidoriExtension* extension) -{ - GtkWidget* web_view = midori_view_get_web_view (view); - g_signal_connect (web_view, "window-object-cleared", - G_CALLBACK (formhistory_window_object_cleared_cb), NULL); - g_signal_connect (web_view, "navigation-policy-decision-requested", - G_CALLBACK (formhistory_navigation_decision_cb), extension); + formhistory_setup_suggestions (web_view, js_context, extension); } static void @@ -278,11 +126,26 @@ formhistory_deactivate_cb (MidoriExtension* extension, MidoriBrowser* browser); static void -formhistory_add_tab_foreach_cb (MidoriView* view, - MidoriBrowser* browser, +formhistory_add_tab_cb (MidoriBrowser* browser, + MidoriView* view, + MidoriExtension* extension) +{ + g_return_if_fail (MIDORI_IS_VIEW (view)); + g_return_if_fail (MIDORI_IS_EXTENSION (extension)); + GtkWidget* web_view = midori_view_get_web_view (view); + + g_signal_connect (web_view, "window-object-cleared", + G_CALLBACK (formhistory_window_object_cleared_cb), extension); + g_signal_connect (web_view, "navigation-policy-decision-requested", + G_CALLBACK (formhistory_navigation_decision_cb), extension); +} + +static void +formhistory_add_tab_foreach_cb (MidoriView* view, MidoriExtension* extension) { - formhistory_add_tab_cb (browser, view, extension); + g_return_if_fail (MIDORI_IS_VIEW (view)); + formhistory_add_tab_cb (NULL, view, extension); } static void @@ -290,6 +153,10 @@ formhistory_app_add_browser_cb (MidoriApp* app, MidoriBrowser* browser, MidoriExtension* extension) { + g_return_if_fail (MIDORI_IS_APP (app)); + g_return_if_fail (MIDORI_IS_BROWSER (browser)); + g_return_if_fail (MIDORI_IS_EXTENSION (extension)); + GtkAccelGroup* acg = gtk_accel_group_new (); GtkActionGroup* action_group = midori_browser_get_action_group (browser); GtkAction* action = gtk_action_new ("FormHistoryToggleState", @@ -318,13 +185,15 @@ formhistory_app_add_browser_cb (MidoriApp* app, } static void -formhistory_deactivate_tabs (MidoriView* view, - MidoriBrowser* browser, - MidoriExtension* extension) +formhistory_deactivate_tab (MidoriView* view, + MidoriExtension* extension) { + g_return_if_fail (MIDORI_IS_VIEW (view)); + g_return_if_fail (MIDORI_IS_EXTENSION (extension)); GtkWidget* web_view = midori_view_get_web_view (view); + g_signal_handlers_disconnect_by_func ( - web_view, formhistory_window_object_cleared_cb, NULL); + web_view, formhistory_window_object_cleared_cb, extension); g_signal_handlers_disconnect_by_func ( web_view, formhistory_navigation_decision_cb, extension); } @@ -334,7 +203,7 @@ formhistory_deactivate_cb (MidoriExtension* extension, MidoriBrowser* browser) { MidoriApp* app = midori_extension_get_app (extension); - sqlite3* db; + FormHistoryPriv* priv = g_object_get_data (G_OBJECT (extension), "priv"); GtkActionGroup* action_group = midori_browser_get_action_group (browser); GtkAction* action; @@ -346,7 +215,7 @@ formhistory_deactivate_cb (MidoriExtension* extension, g_signal_handlers_disconnect_by_func ( app, formhistory_app_add_browser_cb, extension); midori_browser_foreach (browser, - (GtkCallback)formhistory_deactivate_tabs, extension); + (GtkCallback)formhistory_deactivate_tab, extension); g_object_set_data (G_OBJECT (browser), "FormHistoryExtension", NULL); action = gtk_action_group_get_action ( action_group, "FormHistoryToggleState"); @@ -356,40 +225,7 @@ formhistory_deactivate_cb (MidoriExtension* extension, g_object_unref (action); } - katze_assign (jsforms, NULL); - if (global_keys) - g_hash_table_destroy (global_keys); - - if ((db = g_object_get_data (G_OBJECT (extension), "formhistory-db"))) - sqlite3_close (db); -} - -static int -formhistory_add_field (gpointer data, - int argc, - char** argv, - char** colname) -{ - gint i; - gint ncols = 3; - - /* Test whether have the right number of columns */ - g_return_val_if_fail (argc % ncols == 0, 1); - - for (i = 0; i < (argc - ncols) + 1; i++) - { - if (argv[i]) - { - if (colname[i] && !g_ascii_strcasecmp (colname[i], "domain") - && colname[i + 1] && !g_ascii_strcasecmp (colname[i + 1], "field") - && colname[i + 2] && !g_ascii_strcasecmp (colname[i + 2], "value")) - { - gchar* key = argv[i + 1]; - formhistory_update_main_hash (g_strdup (key), g_strdup (argv[i + 2])); - } - } - } - return 0; + formhistory_private_destroy (priv); } static void @@ -402,12 +238,11 @@ formhistory_activate_cb (MidoriExtension* extension, char* errmsg = NULL, *errmsg2 = NULL; KatzeArray* browsers; MidoriBrowser* browser; + FormHistoryPriv* priv; + + priv = formhistory_private_new (); + formhistory_construct_popup_gui (priv); - global_keys = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify)g_free, - (GDestroyNotify)g_free); - if(!jsforms) - formhistory_prepare_js (); config_dir = midori_extension_get_config_dir (extension); katze_mkdir_with_parents (config_dir, 0700); filename = g_build_filename (config_dir, "forms.db", NULL); @@ -421,11 +256,10 @@ formhistory_activate_cb (MidoriExtension* extension, g_free (filename); if ((sqlite3_exec (db, "CREATE TABLE IF NOT EXISTS " "forms (domain text, field text, value text)", - NULL, NULL, &errmsg) == SQLITE_OK) - && (sqlite3_exec (db, "SELECT domain, field, value FROM forms ", - formhistory_add_field, - NULL, &errmsg2) == SQLITE_OK)) - g_object_set_data (G_OBJECT (extension), "formhistory-db", db); + NULL, NULL, &errmsg) == SQLITE_OK)) + { + priv->db = db; + } else { if (errmsg) @@ -441,6 +275,7 @@ formhistory_activate_cb (MidoriExtension* extension, sqlite3_close (db); } + g_object_set_data (G_OBJECT (extension), "priv", priv); browsers = katze_object_get_object (app, "browsers"); KATZE_ARRAY_FOREACH_ITEM (browser, browsers) formhistory_app_add_browser_cb (app, browser, extension); @@ -477,7 +312,7 @@ formhistory_preferences_response_cb (GtkWidget* dialog, KATZE_ARRAY_FOREACH_ITEM (browser, browsers) { midori_browser_foreach (browser, - (GtkCallback)formhistory_deactivate_tabs, extension); + (GtkCallback)formhistory_deactivate_tab, extension); g_signal_handlers_disconnect_by_func ( browser, formhistory_add_tab_cb, extension); @@ -534,7 +369,7 @@ formhistory_toggle_state_cb (GtkAction* action, g_signal_lookup ("window-object-cleared", MIDORI_TYPE_VIEW), 0, NULL, formhistory_window_object_cleared_cb, extension)) { - formhistory_deactivate_tabs (view, browser, extension); + formhistory_deactivate_tab (view, extension); } else { formhistory_add_tab_cb (browser, view, extension); } @@ -560,23 +395,12 @@ formhistory_toggle_state_cb (GtkAction* action, MidoriExtension* extension_init (void) { - gboolean should_init = TRUE; const gchar* ver; gchar* desc; MidoriExtension* extension; - if (formhistory_prepare_js ()) - { - ver = "1.0" MIDORI_VERSION_SUFFIX; - desc = g_strdup (_("Stores history of entered form data")); - } - else - { - desc = g_strdup_printf (_("Not available: %s"), - _("Resource files not installed")); - ver = NULL; - should_init = FALSE; - } + ver = "2.0" MIDORI_VERSION_SUFFIX; + desc = g_strdup (_("Stores history of entered form data")); extension = g_object_new (MIDORI_TYPE_EXTENSION, "name", _("Form history filler"), @@ -587,14 +411,11 @@ extension_init (void) g_free (desc); - if (should_init) - { - midori_extension_install_boolean (extension, "always-load", TRUE); - g_signal_connect (extension, "activate", - G_CALLBACK (formhistory_activate_cb), NULL); - g_signal_connect (extension, "open-preferences", - G_CALLBACK (formhistory_preferences_cb), NULL); - } + midori_extension_install_boolean (extension, "always-load", TRUE); + g_signal_connect (extension, "activate", + G_CALLBACK (formhistory_activate_cb), NULL); + g_signal_connect (extension, "open-preferences", + G_CALLBACK (formhistory_preferences_cb), NULL); return extension; }