changeset 32080:b89351c7580b

safely execute JS scripts only after loading is done. Untested code as of now, will test it in next commit.
author tdrhq@soc.pidgin.im
date Mon, 10 Aug 2009 07:33:21 +0000
parents 7b92a2b852db
children 3bd8fb942ea4
files pidgin/gtkwebview.c pidgin/gtkwebview.h pidgin/plugins/adiumthemes/webkit.c
diffstat 3 files changed, 91 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/pidgin/gtkwebview.c	Mon Aug 10 05:56:08 2009 +0000
+++ b/pidgin/gtkwebview.c	Mon Aug 10 07:33:21 2009 +0000
@@ -41,9 +41,19 @@
 
 static WebKitWebViewClass *parent_class = NULL;
 
+struct GtkWebViewPriv {
+	GHashTable *images; /**< a map from id to temporary file for the image */
+	gboolean    empty;  /**< whether anything has been appended **/
+
+	/* JS execute queue */
+	GQueue *js_queue;
+	gboolean is_loading;
+};
+
 GtkWidget* gtk_webview_new ()
 {
-	return GTK_WIDGET(g_object_new(gtk_webview_get_type(), NULL));
+	GtkWebView* ret = GTK_WEBVIEW (g_object_new(gtk_webview_get_type(), NULL));
+	return GTK_WIDGET (ret);
 }
 
 static char*
@@ -53,10 +63,10 @@
 	FILE *file;
 	PurpleStoredImage* img;
 
-	if (!view->images)
-		view->images = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+	if (!view->priv->images)
+		view->priv->images = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
 	
-	filename = (char*) g_hash_table_lookup (view->images, GINT_TO_POINTER (id));
+	filename = (char*) g_hash_table_lookup (view->priv->images, GINT_TO_POINTER (id));
 	if (filename) return filename;
 			
 	/* else get from img store */
@@ -65,7 +75,7 @@
 	img = purple_imgstore_find_by_id (id);
 
 	fwrite (purple_imgstore_get_data (img), purple_imgstore_get_size (img), 1, file);
-	g_hash_table_insert (view->images, GINT_TO_POINTER (id), filename);
+	g_hash_table_insert (view->priv->images, GINT_TO_POINTER (id), filename);
 	fclose (file);
 	return filename;
 }
@@ -79,9 +89,9 @@
 static void
 clear_images (GtkWebView* view)
 {
-	if (!view->images) return;
-	g_hash_table_foreach (view->images, clear_single_image, NULL);
-	g_hash_table_unref (view->images);
+	if (!view->priv->images) return;
+	g_hash_table_foreach (view->priv->images, clear_single_image, NULL);
+	g_hash_table_unref (view->priv->images);
 }
 
 /*
@@ -146,6 +156,7 @@
 gtk_webview_finalize (GObject *view)
 {
 	clear_images (GTK_WEBVIEW (view));
+	g_free (GTK_WEBVIEW(view)->priv);
 	G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT(view));
 }
 
@@ -173,6 +184,39 @@
 	return TRUE;
 }
 
+static gboolean
+process_js_script_queue (GtkWebView *view)
+{
+	char *script;
+	if (view->priv->is_loading) return FALSE; /* we will be called when loaded */
+	if (!view->priv->js_queue || g_queue_is_empty (view->priv->js_queue))
+		return FALSE; /* nothing to do! */
+
+	script = g_queue_pop_head (view->priv->js_queue);
+	webkit_web_view_execute_script (WEBKIT_WEB_VIEW(view), script);
+	g_free (script);
+
+	return TRUE; /* there may be more for now */
+}
+
+static void 
+webview_load_started (WebKitWebView *view,
+		      WebKitWebFrame *frame,
+		      gpointer userdata)
+{
+	/* is there a better way to test for is_loading? */
+	GTK_WEBVIEW(view)->priv->is_loading = true;
+}
+
+static void
+webview_load_finished (WebKitWebView *view,
+		       WebKitWebFrame *frame,
+		       gpointer userdata)
+{
+	GTK_WEBVIEW(view)->priv->is_loading = false;
+	g_idle_add ((GSourceFunc) process_js_script_queue, view);
+}
+
 char*
 gtk_webview_execute_script (GtkWebView *view, const char *script)
 {
@@ -192,13 +236,30 @@
 	return cstr;
 }
 
+void
+gtk_webview_safe_execute_script (GtkWebView *view, const char* script)
+{
+	g_queue_push_tail (view->priv->js_queue, g_strdup (script));
+	g_idle_add ((GSourceFunc)process_js_script_queue, view);
+}
+
 static void
 gtk_webview_init (GtkWebView *view, gpointer userdata)
 {
+	view->priv = g_new0 (struct GtkWebViewPriv, 1);
 	g_signal_connect (view, "navigation-policy-decision-requested",
 			  G_CALLBACK (webview_link_clicked),
 			  view);
-	view->empty = TRUE;
+
+	g_signal_connect (view, "load-started", 
+			  G_CALLBACK (webview_load_started),
+			  view);
+
+	g_signal_connect (view, "load-finished",
+			  G_CALLBACK (webview_load_finished),
+			  view);
+			  
+	view->priv->empty = TRUE;
 }
 
 
@@ -254,7 +315,7 @@
 	char* script = g_strdup_printf ("document.write(%s)", escaped);
 	printf ("script: %s\n", script);
 	webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), script);
-	view->empty = FALSE;
+	view->priv->empty = FALSE;
 	g_free (script);
 	g_free (escaped);
 }
@@ -273,7 +334,7 @@
 
 gboolean gtk_webview_is_empty (GtkWebView *view)
 {
-	return view->empty;
+	return view->priv->empty;
 }
 
 GType gtk_webview_get_type ()
--- a/pidgin/gtkwebview.h	Mon Aug 10 05:56:08 2009 +0000
+++ b/pidgin/gtkwebview.h	Mon Aug 10 07:33:21 2009 +0000
@@ -35,17 +35,17 @@
 #define GTK_WEBVIEW(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_WEBVIEW, GtkWebView))
 #define GTK_WEBVIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_WEBVIEW, GtkWebViewClass))
 #define GTK_IS_WEBVIEW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_WEBVIEW))
-#define GTK_IS_IMHTML_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_WEBVIEW))
+#define GTK_IS_WEBVIEW_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_WEBVIEW))
 
 
+struct GtkWebViewPriv;
+
 struct _GtkWebView
 {
 	WebKitWebView webkit_web_view;
 
 	/*< private >*/
-	GHashTable *images; /**< a map from id to temporary file for the image */
-	gboolean    empty;  /**< whether anything has been appended **/
-	char *script_return; /**< the last value returned from a script **/
+	struct GtkWebViewPriv* priv;
 };
 
 typedef struct _GtkWebView GtkWebView;
@@ -106,7 +106,8 @@
  * Executes javascript and returns the answer of the script
  * formatted as string. The return value needs to be freed using
  * g_free. If the return values is not required you may instead
- * use webkit_web_view_execute_script.
+ * use webkit_web_view_execute_script, or even better
+ * gtk_webview_safe_execute_script.
  *
  * @param webview The GtkWebView object
  * @param script  The JavaScript to execute
@@ -116,6 +117,18 @@
 char* gtk_webview_execute_script (GtkWebView *webview, const char *script);
 
 /**
+ * Execute the JavaScript only after the webkit_webview_load_string
+ * loads completely. We also guarantee that the scripts are executed
+ * in the order they are called here.This is useful to avoid race
+ * conditions when calls JS functions immediately after opening the
+ * page.
+ *
+ * @param webview the GtkWebView object
+ * @param script   the script to execute
+ */
+void gtk_webview_safe_execute_script (GtkWebView *webview, const char* script);
+
+/**
  * Get the current contents of the GtkWebView object.
  *
  * @param webview The GtkWebView object
--- a/pidgin/plugins/adiumthemes/webkit.c	Mon Aug 10 05:56:08 2009 +0000
+++ b/pidgin/plugins/adiumthemes/webkit.c	Mon Aug 10 07:33:21 2009 +0000
@@ -564,7 +564,7 @@
 	wk_script->script = script;
 	wk_script->webkit = webkit;
 
-	purple_webkit_execute_script (wk_script);
+	g_idle_add (purple_webkit_execute_script, wk_script);
 
 	g_free(smileyed);
 	g_free(msg);