gtk/mobile.cpp | 217 +++++++++++++++++++++++++++++++++++++++++++++++++---- net/FakeSocket.hpp | 2 wsd/LOOLWSD.cpp | 7 + 3 files changed, 210 insertions(+), 16 deletions(-)
New commits: commit 452a0a46c2de3bec3799dd5cf05b81fc1ea49e5f Author: Tor Lillqvist <t...@collabora.com> AuthorDate: Wed Oct 17 22:41:01 2018 +0300 Commit: Tor Lillqvist <t...@collabora.com> CommitDate: Wed Oct 17 22:48:14 2018 +0300 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.) 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(); diff --git a/net/FakeSocket.hpp b/net/FakeSocket.hpp index 5b02f507f..0f80e0a23 100644 --- a/net/FakeSocket.hpp +++ b/net/FakeSocket.hpp @@ -16,9 +16,11 @@ #include <poll.h> +#ifndef __linux #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK 0x100 #endif +#endif void fakeSocketSetLoggingCallback(void (*)(const std::string&)); diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 448b92748..1f0c112b8 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -2610,10 +2610,15 @@ private: /// Process the discovery.xml file and return as string. static std::string getDiscoveryXML() { - // http://server/hosting/discovery +#if defined __linux && defined MOBILEAPP + // discovery.xml is in the top directory + std::string discoveryPath = Path(Application::instance().commandPath()).parent().parent().toString() + "discovery.xml"; +#else std::string discoveryPath = Path(Application::instance().commandPath()).parent().toString() + "discovery.xml"; +#endif if (!File(discoveryPath).exists()) { + // http://server/hosting/discovery.xml discoveryPath = LOOLWSD::FileServerRoot + "/discovery.xml"; } _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits