summaryrefslogtreecommitdiff
path: root/gtk
diff options
context:
space:
mode:
authorTor Lillqvist <tml@collabora.com>2018-10-17 22:41:01 +0300
committerTor Lillqvist <tml@collabora.com>2018-10-17 22:48:14 +0300
commit452a0a46c2de3bec3799dd5cf05b81fc1ea49e5f (patch)
tree56d14a5fba7fdcaeaa3f44d5054a8b365e158c6c /gtk
parentd1e550f01e113feff8819153becb4dd3035c21bf (diff)
More work on the GTK+ testbed app
Add plumbing to send messages from the Online code to the JavaScript code, and vice versa. Similar to what is done for iOS. Sadly, it crashes. Multi-thread issues. Not surprisingly, it crashes when I call webkit_web_view_run_javascript() in another thread than the one where the GTK+ and other Webkit calls were done. I need to come up with some clever way to do everything from the same thread. (On iOS, I use dispatch_async(dispatch_get_main_queue(),...) to schedule a block (i.e., a lambda expression) to be run in the main thread.)
Diffstat (limited to 'gtk')
-rw-r--r--gtk/mobile.cpp217
1 files changed, 202 insertions, 15 deletions
diff --git a/gtk/mobile.cpp b/gtk/mobile.cpp
index cdc711adc..b28e9129b 100644
--- a/gtk/mobile.cpp
+++ b/gtk/mobile.cpp
@@ -28,6 +28,7 @@
*/
#include <iostream>
+#include <thread>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
@@ -35,12 +36,85 @@
#include "FakeSocket.hpp"
#include "Log.hpp"
#include "LOOLWSD.hpp"
+#include "Protocol.hpp"
#include "Util.hpp"
static void destroyWindowCb(GtkWidget* widget, GtkWidget* window);
static gboolean closeWebViewCb(WebKitWebView* webView, GtkWidget* window);
-int loolwsd_server_socket_fd;
+const int SHOW_JS_MAXLEN = 70;
+
+int loolwsd_server_socket_fd = -1;
+
+static std::string fileURL;
+static LOOLWSD *loolwsd = nullptr;
+static int fakeClientFd;
+static int closeNotificationPipeForForwardingThread[2];
+static WebKitWebView *webView;
+
+static void send2JS_ready_callback(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_free(user_data);
+}
+
+static void send2JS(const std::vector<char>& buffer)
+{
+ LOG_TRC_NOFILE("Send to JS: " << LOOLProtocol::getAbbreviatedMessage(buffer.data(), buffer.size()));
+
+ std::string js;
+
+ // Check if the message is binary. We say that any message that isn't just a single line is
+ // "binary" even if that strictly speaking isn't the case; for instance the commandvalues:
+ // message has a long bunch of non-binary JSON on multiple lines. But _onMessage() in Socket.js
+ // handles it fine even if such a message, too, comes in as an ArrayBuffer. (Look for the
+ // "textMsg = String.fromCharCode.apply(null, imgBytes);".)
+
+ const char *newline = (const char *)memchr(buffer.data(), '\n', buffer.size());
+ if (newline != nullptr)
+ {
+ // The data needs to be an ArrayBuffer
+ js = "window.TheFakeWebSocket.onmessage({'data': Base64ToArrayBuffer('";
+ gchar *base64 = g_base64_encode((const guchar*)buffer.data(), buffer.size());
+ js = js + std::string(base64);
+ g_free(base64);
+ js = js + "')});";
+ }
+ else
+ {
+ const unsigned char *ubufp = (const unsigned char *)buffer.data();
+ std::vector<char> data;
+ for (int i = 0; i < buffer.size(); i++)
+ {
+ if (ubufp[i] < ' ' || ubufp[i] == '\'' || ubufp[i] == '\\')
+ {
+ data.push_back('\\');
+ data.push_back('x');
+ data.push_back("0123456789abcdef"[(ubufp[i] >> 4) & 0x0F]);
+ data.push_back("0123456789abcdef"[ubufp[i] & 0x0F]);
+ }
+ else
+ {
+ data.push_back(ubufp[i]);
+ }
+ }
+ data.push_back(0);
+
+ js = "window.TheFakeWebSocket.onmessage({'data': '";
+ js = js + std::string(buffer.data());
+ js = js + "'});";
+ }
+
+ std::string subjs = js.substr(0, std::min(std::string::size_type(SHOW_JS_MAXLEN), js.length()));
+ if (js.length() > SHOW_JS_MAXLEN)
+ subjs += "...";
+
+ LOG_TRC_NOFILE( "Evaluating JavaScript: " << subjs);
+
+ char *jscopy = strdup(js.c_str());
+ webkit_web_view_run_javascript(webView, jscopy, NULL, send2JS_ready_callback, jscopy);
+}
static void handle_debug_message(WebKitUserContentManager *manager,
WebKitJavascriptResult *js_result,
@@ -48,9 +122,9 @@ static void handle_debug_message(WebKitUserContentManager *manager,
{
JSCValue *value = webkit_javascript_result_get_js_value(js_result);
if (jsc_value_is_string(value))
- std::cout << "From JS: debug: " << jsc_value_to_string(value) << std::endl;
+ LOG_TRC_NOFILE("From JS: debug: " << jsc_value_to_string(value));
else
- std::cout << "From JS: debug: some object" << std::endl;
+ LOG_TRC_NOFILE("From JS: debug: some object");
}
static void handle_lool_message(WebKitUserContentManager *manager,
@@ -58,10 +132,102 @@ static void handle_lool_message(WebKitUserContentManager *manager,
gpointer user_data)
{
JSCValue *value = webkit_javascript_result_get_js_value(js_result);
+
if (jsc_value_is_string(value))
- std::cout << "From JS: lool: " << jsc_value_to_string(value) << std::endl;
+ {
+ gchar *string_value = jsc_value_to_string(value);
+
+ LOG_TRC_NOFILE("From JS: lool: " << string_value);
+
+ if (strcmp(string_value, "HULLO") == 0)
+ {
+ // Now we know that the JS has started completely
+
+ // Contact the permanently (during app lifetime) listening LOOLWSD server
+ // "public" socket
+ assert(loolwsd_server_socket_fd != -1);
+ int rc = fakeSocketConnect(fakeClientFd, loolwsd_server_socket_fd);
+ assert(rc != -1);
+
+ // Create a socket pair to notify the below thread when the document has been closed
+ fakeSocketPipe2(closeNotificationPipeForForwardingThread);
+
+ // Start another thread to read responses and forward them to the JavaScript
+ std::thread([]
+ {
+ Util::setThreadName("app2js");
+ while (true)
+ {
+ struct pollfd pollfd[2];
+ pollfd[0].fd = fakeClientFd;
+ pollfd[0].events = POLLIN;
+ pollfd[1].fd = closeNotificationPipeForForwardingThread[1];
+ pollfd[1].events = POLLIN;
+ if (fakeSocketPoll(pollfd, 2, -1) > 0)
+ {
+ if (pollfd[1].revents == POLLIN)
+ {
+ // The code below handling the "BYE" fake Websocket
+ // message has closed the other end of the
+ // closeNotificationPipeForForwardingThread. Let's close
+ // the other end too just for cleanliness, even if a
+ // FakeSocket as such is not a system resource so nothing
+ // is saved by closing it.
+ fakeSocketClose(closeNotificationPipeForForwardingThread[1]);
+
+ // Close our end of the fake socket connection to the
+ // ClientSession thread, so that it terminates
+ fakeSocketClose(fakeClientFd);
+
+ return;
+ }
+ if (pollfd[0].revents == POLLIN)
+ {
+ int n = fakeSocketAvailableDataLength(fakeClientFd);
+ if (n == 0)
+ return;
+ std::vector<char> buf(n);
+ n = fakeSocketRead(fakeClientFd, buf.data(), n);
+ send2JS(buf);
+ }
+ }
+ else
+ break;
+ }
+ assert(false);
+ }).detach();
+
+ // First we simply send it the URL. This corresponds to the GET request with Upgrade to
+ // WebSocket.
+ LOG_TRC_NOFILE("Actually sending to Online:" << fileURL);
+
+ struct pollfd pollfd;
+ pollfd.fd = fakeClientFd;
+ pollfd.events = POLLOUT;
+ fakeSocketPoll(&pollfd, 1, -1);
+ fakeSocketWrite(fakeClientFd, fileURL.c_str(), fileURL.size());
+ }
+ else if (strcmp(string_value, "BYE") == 0)
+ {
+ LOG_TRC_NOFILE("Document window terminating on JavaScript side. Closing our end of the socket.");
+
+ // Close one end of the socket pair, that will wake up the forwarding thread above
+ fakeSocketClose(closeNotificationPipeForForwardingThread[0]);
+
+ // ???
+ }
+ else
+ {
+ struct pollfd pollfd;
+ pollfd.fd = fakeClientFd;
+ pollfd.events = POLLOUT;
+ fakeSocketPoll(&pollfd, 1, -1);
+ fakeSocketWrite(fakeClientFd, string_value, strlen(string_value));
+ }
+ g_free(string_value);
+ }
else
- std::cout << "From JS: lool: some object" << std::endl;
+ LOG_TRC_NOFILE("From JS: lool: some object");
}
static void handle_error_message(WebKitUserContentManager *manager,
@@ -70,9 +236,9 @@ static void handle_error_message(WebKitUserContentManager *manager,
{
JSCValue *value = webkit_javascript_result_get_js_value(js_result);
if (jsc_value_is_string(value))
- std::cout << "From JS: error: " << jsc_value_to_string(value) << std::endl;
+ LOG_TRC_NOFILE("From JS: error: " << jsc_value_to_string(value));
else
- std::cout << "From JS: error: some object" << std::endl;
+ LOG_TRC_NOFILE("From JS: error: some object");
}
int main(int argc, char* argv[])
@@ -84,6 +250,24 @@ int main(int argc, char* argv[])
LOG_TRC_NOFILE(line);
});
+ std::thread([]
+ {
+ assert(loolwsd == nullptr);
+ char *argv[2];
+ argv[0] = strdup("mobile");
+ argv[1] = nullptr;
+ Util::setThreadName("app");
+ while (true)
+ {
+ loolwsd = new LOOLWSD();
+ loolwsd->run(1, argv);
+ delete loolwsd;
+ LOG_TRC("One run of LOOLWSD completed");
+ }
+ }).detach();
+
+ fakeClientFd = fakeSocketSocket();
+
// Initialize GTK+
gtk_init(&argc, &argv);
@@ -103,7 +287,7 @@ int main(int argc, char* argv[])
webkit_user_content_manager_register_script_message_handler(userContentManager, "error");
// Create a browser instance
- WebKitWebView *webView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(userContentManager));
+ webView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(userContentManager));
// Put the browser area into the main window
gtk_container_add(GTK_CONTAINER(main_window), GTK_WIDGET(webView));
@@ -113,13 +297,17 @@ int main(int argc, char* argv[])
g_signal_connect(main_window, "destroy", G_CALLBACK(destroyWindowCb), NULL);
g_signal_connect(webView, "close", G_CALLBACK(closeWebViewCb), main_window);
+ fileURL = "file://" TOPSRCDIR "/test/data/hello-world.odt";
+
+ std::string urlAndQuery =
+ "file://" TOPSRCDIR "/loleaflet/dist/loleaflet.html"
+ "?file_path=" + fileURL +
+ "&closebutton=1"
+ "&permission=edit"
+ "&debug=true";
+
// Load a web page into the browser instance
- webkit_web_view_load_uri(webView,
- "file://" TOPSRCDIR "/loleaflet/dist/loleaflet.html"
- "?file_path=" TOPSRCDIR "/test/data/hello-world.odt"
- "&closebutton=1"
- "&permission=edit"
- "&debug=true");
+ webkit_web_view_load_uri(webView, urlAndQuery.c_str());
// Make sure that when the browser area becomes visible, it will get mouse
// and keyboard events
@@ -134,7 +322,6 @@ int main(int argc, char* argv[])
return 0;
}
-
static void destroyWindowCb(GtkWidget* widget, GtkWidget* window)
{
gtk_main_quit();