blob: 6aa92e9f6077da974246ca96de78551af3fba4e6 [file] [log] [blame]
aviau039001d2016-09-29 16:39:05 -04001/*
2 * Copyright (C) 2016 Savoir-faire Linux Inc.
3 * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "webkitchatcontainer.h"
21
22// GTK+ related
23#include <gtk/gtk.h>
aviau039001d2016-09-29 16:39:05 -040024#include <webkit2/webkit2.h>
25
26// Qt
27#include <QtCore/QJsonValue>
28#include <QtCore/QJsonObject>
29#include <QtCore/QJsonDocument>
30
31// LRC
32#include <media/textrecording.h>
33#include <globalinstances.h>
aviaufc213552016-11-01 12:39:39 -040034#include <contactmethod.h>
aviau039001d2016-09-29 16:39:05 -040035
36// Ring Client
37#include "native/pixbufmanipulator.h"
38#include "config.h"
39
40struct _WebKitChatContainer
41{
42 GtkBox parent;
43};
44
45struct _WebKitChatContainerClass
46{
47 GtkBoxClass parent_class;
48};
49
50typedef struct _WebKitChatContainerPrivate WebKitChatContainerPrivate;
51
52struct _WebKitChatContainerPrivate
53{
54 GtkWidget* webview_chat;
aviau32b8dd62016-11-02 00:36:57 -040055 GtkWidget* box_webview_chat;
aviau039001d2016-09-29 16:39:05 -040056
57 bool chatview_debug;
58
59 /* Array of javascript libraries to load. Used during initialization */
60 GList* js_libs_to_load;
61 gboolean js_libs_loaded;
62};
63
64G_DEFINE_TYPE_WITH_PRIVATE(WebKitChatContainer, webkit_chat_container, GTK_TYPE_BOX);
65
66#define WEBKIT_CHAT_CONTAINER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), WEBKIT_CHAT_CONTAINER_TYPE, WebKitChatContainerPrivate))
67
68/* signals */
69enum {
70 READY,
AmarOkb4253242017-07-13 11:21:39 -040071 SEND_MESSAGE,
aviau039001d2016-09-29 16:39:05 -040072 LAST_SIGNAL
73};
74
75static guint webkit_chat_container_signals[LAST_SIGNAL] = { 0 };
76
Stepan Salenikovich1cf35e52017-05-03 16:12:11 -040077/* functions */
78static gboolean webview_crashed(WebKitChatContainer *self);
79
aviau039001d2016-09-29 16:39:05 -040080static void
81webkit_chat_container_dispose(GObject *object)
82{
83 G_OBJECT_CLASS(webkit_chat_container_parent_class)->dispose(object);
84}
85
86static void
87webkit_chat_container_init(WebKitChatContainer *view)
88{
89 gtk_widget_init_template(GTK_WIDGET(view));
90}
91
92static void
93webkit_chat_container_class_init(WebKitChatContainerClass *klass)
94{
95 G_OBJECT_CLASS(klass)->dispose = webkit_chat_container_dispose;
96
aviau039001d2016-09-29 16:39:05 -040097 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
98 "/cx/ring/RingGnome/webkitchatcontainer.ui");
99
aviau32b8dd62016-11-02 00:36:57 -0400100 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), WebKitChatContainer, box_webview_chat);
aviau039001d2016-09-29 16:39:05 -0400101
102 /* add signals */
103 webkit_chat_container_signals[READY] = g_signal_new("ready",
104 G_TYPE_FROM_CLASS(klass),
105 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
106 0,
107 nullptr,
108 nullptr,
109 g_cclosure_marshal_VOID__VOID,
110 G_TYPE_NONE, 0);
AmarOkb4253242017-07-13 11:21:39 -0400111
112 webkit_chat_container_signals[SEND_MESSAGE] = g_signal_new("send-message",
113 G_TYPE_FROM_CLASS(klass),
114 (GSignalFlags) (G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED),
115 0,
116 nullptr,
117 nullptr,
118 g_cclosure_marshal_VOID__STRING,
119 G_TYPE_NONE, 1, G_TYPE_STRING);
aviau039001d2016-09-29 16:39:05 -0400120}
121
122static gboolean
123webview_chat_context_menu(WebKitChatContainer *self)
124{
125 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(self);
126 return !priv->chatview_debug;
127}
128
129QString
130message_index_to_json_message_object(const QModelIndex &idx)
131{
132 auto message = idx.data().value<QString>();
133 auto sender = idx.data(static_cast<int>(Media::TextRecording::Role::AuthorDisplayname)).value<QString>();
aviaufc213552016-11-01 12:39:39 -0400134 auto sender_contact_method = idx.data(static_cast<int>(Media::TextRecording::Role::ContactMethod)).value<ContactMethod*>();
aviau039001d2016-09-29 16:39:05 -0400135 auto timestamp = idx.data(static_cast<int>(Media::TextRecording::Role::Timestamp)).value<time_t>();
136 auto direction = idx.data(static_cast<int>(Media::TextRecording::Role::Direction)).value<Media::Media::Direction>();
137 auto message_id = idx.row();
138
aviaufc213552016-11-01 12:39:39 -0400139 QString sender_contact_method_str;
140 if(direction == Media::Media::Direction::IN)
141 {
142 sender_contact_method_str = QString(g_strdup_printf("%p", sender_contact_method));
143 }
144 else
145 {
146 sender_contact_method_str = "self";
147 }
148
aviau039001d2016-09-29 16:39:05 -0400149 QJsonObject message_object = QJsonObject();
150 message_object.insert("text", QJsonValue(message));
151 message_object.insert("id", QJsonValue(QString().setNum(message_id)));
152 message_object.insert("sender", QJsonValue(sender));
aviaufc213552016-11-01 12:39:39 -0400153 message_object.insert("sender_contact_method", QJsonValue(sender_contact_method_str));
aviau039001d2016-09-29 16:39:05 -0400154 message_object.insert("timestamp", QJsonValue((int) timestamp));
155 message_object.insert("direction", QJsonValue((direction == Media::Media::Direction::IN) ? "in" : "out"));
156
157 switch(idx.data(static_cast<int>(Media::TextRecording::Role::DeliveryStatus)).value<Media::TextRecording::Status>())
158 {
159 case Media::TextRecording::Status::FAILURE:
160 {
161 message_object.insert("delivery_status", QJsonValue("failure"));
162 break;
163 }
164 case Media::TextRecording::Status::COUNT__:
165 {
166 message_object.insert("delivery_status", QJsonValue("count__"));
167 break;
168 }
169 case Media::TextRecording::Status::SENDING:
170 {
171 message_object.insert("delivery_status", QJsonValue("sending"));
172 break;
173 }
174 case Media::TextRecording::Status::UNKNOWN:
175 {
176 message_object.insert("delivery_status", QJsonValue("unknown"));
177 break;
178 }
179 case Media::TextRecording::Status::READ:
180 {
181 message_object.insert("delivery_status", QJsonValue("read"));
182 break;
183 }
184 case Media::TextRecording::Status::SENT:
185 {
186 message_object.insert("delivery_status", QJsonValue("sent"));
187 break;
188 }
189 }
190
191 return QString(QJsonDocument(message_object).toJson(QJsonDocument::Compact));
192}
193
Stepan Salenikovichb52dbc52016-12-27 14:05:39 -0500194#if WEBKIT_CHECK_VERSION(2, 6, 0)
aviau039001d2016-09-29 16:39:05 -0400195static gboolean
196webview_chat_decide_policy (G_GNUC_UNUSED WebKitWebView *web_view,
197 WebKitPolicyDecision *decision,
198 WebKitPolicyDecisionType type)
199{
200 switch (type)
201 {
202 case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
203 case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
204 {
205 WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
206 WebKitNavigationAction* navigation_action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
207 WebKitNavigationType navigation_type = webkit_navigation_action_get_navigation_type(navigation_action);
208
209 switch (navigation_type)
210 {
211 case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:
212 case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:
213 case WEBKIT_NAVIGATION_TYPE_RELOAD:
214 case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
215 case WEBKIT_NAVIGATION_TYPE_OTHER:
216 {
217 /* make no decision */
218 return FALSE;
219
220 }
221 case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:
222 {
223 webkit_policy_decision_ignore(decision);
224
225 WebKitURIRequest* uri_request = webkit_navigation_action_get_request(navigation_action);
226 const gchar* uri = webkit_uri_request_get_uri(uri_request);
227
228 gtk_show_uri(NULL, uri, GDK_CURRENT_TIME, NULL);
229 }
230 }
231
232 webkit_policy_decision_ignore(decision);
233 break;
234 }
235 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
236 {
237 //WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);
238 //break;
239 return FALSE;
240 }
241 default:
242 {
243 /* Making no decision results in webkit_policy_decision_use(). */
244 return FALSE;
245 }
246 }
247 return TRUE;
248}
249#endif
250
AmarOkb4253242017-07-13 11:21:39 -0400251static gboolean
252webview_send_text(WebKitWebView *self,
253 WebKitScriptDialog *dialog,
254 gpointer user_data)
255{
256 auto message = webkit_script_dialog_get_message(dialog);
257 g_signal_emit(G_OBJECT(self), webkit_chat_container_signals[SEND_MESSAGE], 0, message);
258 return true;
259}
260
aviau039001d2016-09-29 16:39:05 -0400261static void
262javascript_library_loaded(WebKitWebView *webview_chat,
263 GAsyncResult *result,
264 WebKitChatContainer* self)
265{
266 g_return_if_fail(IS_WEBKIT_CHAT_CONTAINER(self));
267 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(self);
268
269 auto loaded_library = g_list_first(priv->js_libs_to_load);
270
271 GError *error = NULL;
272 WebKitJavascriptResult* js_result = webkit_web_view_run_javascript_from_gresource_finish(webview_chat, result, &error);
273 if (!js_result) {
274 g_warning("Error loading %s: %s", (const gchar*) loaded_library->data, error->message);
275 g_error_free(error);
276 g_object_unref(self);
277 /* Stop loading view, most likely resulting in a blank page */
278 return;
279 }
280 webkit_javascript_result_unref(js_result);
281
282 priv->js_libs_to_load = g_list_remove(priv->js_libs_to_load, loaded_library->data);
283
284 if(g_list_length(priv->js_libs_to_load) > 0)
285 {
286 /* keep loading... */
287 webkit_web_view_run_javascript_from_gresource(
288 webview_chat,
289 (const gchar*) g_list_first(priv->js_libs_to_load)->data,
290 NULL,
291 (GAsyncReadyCallback) javascript_library_loaded,
292 self
293 );
294 }
295 else
296 {
297 priv->js_libs_loaded = TRUE;
298 g_signal_emit(G_OBJECT(self), webkit_chat_container_signals[READY], 0);
299
300 /* The view could now be deleted without causing a crash */
301 g_object_unref(self);
302 }
303}
304
305static void
306load_javascript_libs(WebKitWebView *webview_chat,
307 WebKitChatContainer* self)
308{
309 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(self);
310
311 /* Create the list of libraries to load */
aviau039001d2016-09-29 16:39:05 -0400312 priv->js_libs_to_load = g_list_append(priv->js_libs_to_load, (gchar*) "/cx/ring/RingGnome/linkify.js");
313 priv->js_libs_to_load = g_list_append(priv->js_libs_to_load, (gchar*) "/cx/ring/RingGnome/linkify-string.js");
314 priv->js_libs_to_load = g_list_append(priv->js_libs_to_load, (gchar*) "/cx/ring/RingGnome/linkify-html.js");
aviau039001d2016-09-29 16:39:05 -0400315
316 /* ref the chat view so that its not destroyed while we load
317 * we will unref in javascript_library_loaded
318 */
319 g_object_ref(self);
320
321 /* start loading */
322 webkit_web_view_run_javascript_from_gresource(
323 WEBKIT_WEB_VIEW(webview_chat),
324 (const gchar*) g_list_first(priv->js_libs_to_load)->data,
325 NULL,
326 (GAsyncReadyCallback) javascript_library_loaded,
327 self
328 );
329}
330
331static void
332webview_chat_load_changed(WebKitWebView *webview_chat,
333 WebKitLoadEvent load_event,
334 WebKitChatContainer* self)
335{
336 switch (load_event) {
337 case WEBKIT_LOAD_REDIRECTED:
338 {
339 g_warning("webview_chat load is being redirected, this should not happen");
340 }
341 case WEBKIT_LOAD_STARTED:
342 case WEBKIT_LOAD_COMMITTED:
343 {
344 break;
345 }
346 case WEBKIT_LOAD_FINISHED:
347 {
348 load_javascript_libs(webview_chat, self);
349 //TODO: disconnect? It shouldn't happen more than once
350 break;
351 }
352 }
353}
354
355static void
356build_view(WebKitChatContainer *view)
357{
358 g_return_if_fail(IS_WEBKIT_CHAT_CONTAINER(view));
359 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
360
aviau039001d2016-09-29 16:39:05 -0400361 priv->chatview_debug = FALSE;
362 auto ring_chatview_debug = g_getenv("RING_CHATVIEW_DEBUG");
363 if (ring_chatview_debug || g_strcmp0(ring_chatview_debug, "true") == 0)
364 {
365 priv->chatview_debug = TRUE;
366 }
367
aviau32b8dd62016-11-02 00:36:57 -0400368 /* Prepare WebKitUserContentManager */
369 WebKitUserContentManager* webkit_content_manager = webkit_user_content_manager_new();
370
371 WebKitUserStyleSheet* chatview_style_sheet = webkit_user_style_sheet_new(
372 (gchar*) g_bytes_get_data(
373 g_resources_lookup_data(
374 "/cx/ring/RingGnome/chatview.css",
375 G_RESOURCE_LOOKUP_FLAGS_NONE,
376 NULL
377 ),
378 NULL
379 ),
380 WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
381 WEBKIT_USER_STYLE_LEVEL_USER,
382 NULL,
383 NULL
384 );
385 webkit_user_content_manager_add_style_sheet(webkit_content_manager, chatview_style_sheet);
386
387 /* Prepare WebKitSettings */
aviau039001d2016-09-29 16:39:05 -0400388 WebKitSettings* webkit_settings = webkit_settings_new_with_settings(
389 "enable-javascript", TRUE,
390 "enable-developer-extras", priv->chatview_debug,
391 "enable-java", FALSE,
392 "enable-plugins", FALSE,
393 "enable-site-specific-quirks", FALSE,
394 "enable-smooth-scrolling", TRUE,
395 NULL
396 );
aviau32b8dd62016-11-02 00:36:57 -0400397
398 /* Create the WebKitWebView */
399 priv->webview_chat = GTK_WIDGET(
400 webkit_web_view_new_with_user_content_manager(
401 webkit_content_manager
402 )
403 );
404
405 gtk_container_add(GTK_CONTAINER(priv->box_webview_chat), priv->webview_chat);
406 gtk_widget_show(priv->webview_chat);
407 gtk_widget_set_vexpand(GTK_WIDGET(priv->webview_chat), TRUE);
408 gtk_widget_set_hexpand(GTK_WIDGET(priv->webview_chat), TRUE);
409
410 /* Set the WebKitSettings */
aviau039001d2016-09-29 16:39:05 -0400411 webkit_web_view_set_settings(WEBKIT_WEB_VIEW(priv->webview_chat), webkit_settings);
412
413 g_signal_connect(priv->webview_chat, "load-changed", G_CALLBACK(webview_chat_load_changed), view);
414 g_signal_connect_swapped(priv->webview_chat, "context-menu", G_CALLBACK(webview_chat_context_menu), view);
AmarOkb4253242017-07-13 11:21:39 -0400415 g_signal_connect_swapped(priv->webview_chat, "script-dialog", G_CALLBACK(webview_send_text), view);
Stepan Salenikovichb52dbc52016-12-27 14:05:39 -0500416#if WEBKIT_CHECK_VERSION(2, 6, 0)
aviau039001d2016-09-29 16:39:05 -0400417 g_signal_connect(priv->webview_chat, "decide-policy", G_CALLBACK(webview_chat_decide_policy), view);
418#endif
419
420 GBytes* chatview_bytes = g_resources_lookup_data(
421 "/cx/ring/RingGnome/chatview.html",
422 G_RESOURCE_LOOKUP_FLAGS_NONE,
423 NULL
424 );
425
426 webkit_web_view_load_html(
427 WEBKIT_WEB_VIEW(priv->webview_chat),
428 (gchar*) g_bytes_get_data(chatview_bytes, NULL),
429 NULL
430 );
431
432 /* Now we wait for the load-changed event, before we
433 * start loading javascript libraries */
Stepan Salenikovich1cf35e52017-05-03 16:12:11 -0400434
435 /* handle web view crash */
436 g_signal_connect_swapped(priv->webview_chat, "web-process-crashed", G_CALLBACK(webview_crashed), view);
437}
438
439static gboolean
440webview_crashed(WebKitChatContainer *self)
441{
442 g_warning("Gtk Web Process crashed! Re-createing web view");
443
444 auto priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(self);
445
446 /* make sure we destroy previous WebView */
447 if (priv->webview_chat) {
448 gtk_widget_destroy(priv->webview_chat);
449 priv->webview_chat = nullptr;
450 }
451
452 build_view(self);
453
454 return G_SOURCE_CONTINUE;
aviau039001d2016-09-29 16:39:05 -0400455}
456
457GtkWidget *
458webkit_chat_container_new()
459{
460 gpointer view = g_object_new(WEBKIT_CHAT_CONTAINER_TYPE, NULL);
461
462 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
463 priv->js_libs_loaded = FALSE;
464
465 build_view(WEBKIT_CHAT_CONTAINER(view));
466
467 return (GtkWidget *)view;
468}
469
470void
aviaufc213552016-11-01 12:39:39 -0400471webkit_chat_container_clear_sender_images(WebKitChatContainer *view)
472{
473 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
474
475 webkit_web_view_run_javascript(
476 WEBKIT_WEB_VIEW(priv->webview_chat),
477 "ring.chatview.clearSenderImages()",
478 NULL,
479 NULL,
480 NULL
481 );
482}
483
484void
aviau039001d2016-09-29 16:39:05 -0400485webkit_chat_container_clear(WebKitChatContainer *view)
486{
487 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
488
489 webkit_web_view_run_javascript(
490 WEBKIT_WEB_VIEW(priv->webview_chat),
491 "ring.chatview.clearMessages()",
492 NULL,
493 NULL,
494 NULL
495 );
496
aviaufc213552016-11-01 12:39:39 -0400497 webkit_chat_container_clear_sender_images(view);
aviau039001d2016-09-29 16:39:05 -0400498}
499
500void
501webkit_chat_container_print_new_message(WebKitChatContainer *view, const QModelIndex &idx)
502{
503 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
504
Guillaume Roguez1820ba12017-03-28 16:30:36 -0400505 auto message_object = message_index_to_json_message_object(idx).toUtf8();
506 gchar* function_call = g_strdup_printf("ring.chatview.addMessage(%s);", message_object.constData());
aviau039001d2016-09-29 16:39:05 -0400507 webkit_web_view_run_javascript(
508 WEBKIT_WEB_VIEW(priv->webview_chat),
509 function_call,
510 NULL,
511 NULL,
512 NULL
513 );
514 g_free(function_call);
515}
516
517void
518webkit_chat_container_update_message(WebKitChatContainer *view, const QModelIndex &idx)
519{
520 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
521
Guillaume Roguez1820ba12017-03-28 16:30:36 -0400522 auto message_object = message_index_to_json_message_object(idx).toUtf8();
523 gchar* function_call = g_strdup_printf("ring.chatview.updateMessage(%s);", message_object.constData());
aviau039001d2016-09-29 16:39:05 -0400524 webkit_web_view_run_javascript(
525 WEBKIT_WEB_VIEW(priv->webview_chat),
526 function_call,
527 NULL,
528 NULL,
529 NULL
530 );
531 g_free(function_call);
532}
533
534void
aviaufc213552016-11-01 12:39:39 -0400535webkit_chat_container_set_sender_image(WebKitChatContainer *view, ContactMethod *sender_contact_method, QVariant sender_image)
aviau039001d2016-09-29 16:39:05 -0400536{
537 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
538
aviaufc213552016-11-01 12:39:39 -0400539 /* The sender_contact_method should be set to nullptr if the sender is self */
540 QString sender_contact_method_str;
541 if (sender_contact_method)
542 {
aviauac74ac32016-11-03 17:25:11 -0400543 sender_contact_method_str = QString().sprintf("%p", sender_contact_method);
aviaufc213552016-11-01 12:39:39 -0400544 }
545 else
546 {
547 sender_contact_method_str = "self";
548 }
549
aviau039001d2016-09-29 16:39:05 -0400550 auto sender_image_base64 = (QString) GlobalInstances::pixmapManipulator().toByteArray(sender_image).toBase64();
551
552 QJsonObject set_sender_image_object = QJsonObject();
aviaufc213552016-11-01 12:39:39 -0400553 set_sender_image_object.insert("sender_contact_method", QJsonValue(sender_contact_method_str));
aviau039001d2016-09-29 16:39:05 -0400554 set_sender_image_object.insert("sender_image", QJsonValue(sender_image_base64));
555
Stepan Salenikovich407dc412016-11-29 11:01:31 -0500556 auto set_sender_image_object_string = QString(QJsonDocument(set_sender_image_object).toJson(QJsonDocument::Compact));
aviau039001d2016-09-29 16:39:05 -0400557
Stepan Salenikovich407dc412016-11-29 11:01:31 -0500558 gchar* function_call = g_strdup_printf("ring.chatview.setSenderImage(%s);", set_sender_image_object_string.toUtf8().constData());
aviau039001d2016-09-29 16:39:05 -0400559 webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(priv->webview_chat), function_call, NULL, NULL, NULL);
560 g_free(function_call);
561}
562
563gboolean
564webkit_chat_container_is_ready(WebKitChatContainer *view)
565{
566 WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
567 return priv->js_libs_loaded;
568}