/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { //============================================================================== class WebKitSymbols : public DeletedAtShutdown { public: //============================================================================== bool isWebKitAvailable() { return webKitIsAvailable; } //============================================================================== JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_new, juce_webkit_settings_new, (), WebKitSettings*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_hardware_acceleration_policy, juce_webkit_settings_set_hardware_acceleration_policy, (WebKitSettings*, int), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_new_with_settings, juce_webkit_web_view_new_with_settings, (WebKitSettings*), GtkWidget*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_load_uri, juce_webkit_web_view_load_uri, (WebKitWebView*, const gchar*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_use, juce_webkit_policy_decision_use, (WebKitPolicyDecision*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_ignore, juce_webkit_policy_decision_ignore, (WebKitPolicyDecision*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_back, juce_webkit_web_view_go_back, (WebKitWebView*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_forward, juce_webkit_web_view_go_forward, (WebKitWebView*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_reload, juce_webkit_web_view_reload, (WebKitWebView*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_stop_loading, juce_webkit_web_view_stop_loading, (WebKitWebView*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_request_get_uri, juce_webkit_uri_request_get_uri, (WebKitURIRequest*), const gchar*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_action_get_request, juce_webkit_navigation_action_get_request, (WebKitNavigationAction*), WebKitURIRequest*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_frame_name, juce_webkit_navigation_policy_decision_get_frame_name, (WebKitNavigationPolicyDecision*), const gchar*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_navigation_action, juce_webkit_navigation_policy_decision_get_navigation_action, (WebKitNavigationPolicyDecision*), WebKitNavigationAction*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_uri, juce_webkit_web_view_get_uri, (WebKitWebView*), const gchar*) //============================================================================== JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_init, juce_gtk_init, (int*, char***), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_new, juce_gtk_plug_new, (::Window), GtkWidget*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_scrolled_window_new, juce_gtk_scrolled_window_new, (GtkAdjustment*, GtkAdjustment*), GtkWidget*) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_container_add, juce_gtk_container_add, (GtkContainer*, GtkWidget*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_widget_show_all, juce_gtk_widget_show_all, (GtkWidget*), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_get_id, juce_gtk_plug_get_id, (GtkPlug*), ::Window) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main, juce_gtk_main, (), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main_quit, juce_gtk_main_quit, (), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_unix_fd_add, juce_g_unix_fd_add, (gint, GIOCondition, GUnixFDSourceFunc, gpointer), guint) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_ref, juce_g_object_ref, (gpointer), gpointer) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_unref, juce_g_object_unref, (gpointer), void) JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_signal_connect_data, juce_g_signal_connect_data, (gpointer, const gchar*, GCallback, gpointer, GClosureNotify, GConnectFlags), gulong) //============================================================================== JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WebKitSymbols) private: WebKitSymbols() = default; ~WebKitSymbols() { clearSingletonInstance(); } template struct SymbolBinding { FuncPtr& func; const char* name; }; template SymbolBinding makeSymbolBinding (FuncPtr& func, const char* name) { return { func, name }; } template bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding) { if (auto* func = lib.getFunction (binding.name)) { binding.func = reinterpret_cast (func); return true; } return false; } template bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding, Args... args) { return loadSymbols (lib, binding) && loadSymbols (lib, args...); } //============================================================================== bool loadWebkitSymbols() { return loadSymbols (webkitLib, makeSymbolBinding (juce_webkit_settings_new, "webkit_settings_new"), makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy, "webkit_settings_set_hardware_acceleration_policy"), makeSymbolBinding (juce_webkit_web_view_new_with_settings, "webkit_web_view_new_with_settings"), makeSymbolBinding (juce_webkit_policy_decision_use, "webkit_policy_decision_use"), makeSymbolBinding (juce_webkit_policy_decision_ignore, "webkit_policy_decision_ignore"), makeSymbolBinding (juce_webkit_web_view_go_back, "webkit_web_view_go_back"), makeSymbolBinding (juce_webkit_web_view_go_forward, "webkit_web_view_go_forward"), makeSymbolBinding (juce_webkit_web_view_reload, "webkit_web_view_reload"), makeSymbolBinding (juce_webkit_web_view_stop_loading, "webkit_web_view_stop_loading"), makeSymbolBinding (juce_webkit_uri_request_get_uri, "webkit_uri_request_get_uri"), makeSymbolBinding (juce_webkit_web_view_load_uri, "webkit_web_view_load_uri"), makeSymbolBinding (juce_webkit_navigation_action_get_request, "webkit_navigation_action_get_request"), makeSymbolBinding (juce_webkit_navigation_policy_decision_get_frame_name, "webkit_navigation_policy_decision_get_frame_name"), makeSymbolBinding (juce_webkit_navigation_policy_decision_get_navigation_action, "webkit_navigation_policy_decision_get_navigation_action"), makeSymbolBinding (juce_webkit_web_view_get_uri, "webkit_web_view_get_uri")); } bool loadGtkSymbols() { return loadSymbols (gtkLib, makeSymbolBinding (juce_gtk_init, "gtk_init"), makeSymbolBinding (juce_gtk_plug_new, "gtk_plug_new"), makeSymbolBinding (juce_gtk_scrolled_window_new, "gtk_scrolled_window_new"), makeSymbolBinding (juce_gtk_container_add, "gtk_container_add"), makeSymbolBinding (juce_gtk_widget_show_all, "gtk_widget_show_all"), makeSymbolBinding (juce_gtk_plug_get_id, "gtk_plug_get_id"), makeSymbolBinding (juce_gtk_main, "gtk_main"), makeSymbolBinding (juce_gtk_main_quit, "gtk_main_quit"), makeSymbolBinding (juce_g_unix_fd_add, "g_unix_fd_add"), makeSymbolBinding (juce_g_object_ref, "g_object_ref"), makeSymbolBinding (juce_g_object_unref, "g_object_unref"), makeSymbolBinding (juce_g_signal_connect_data, "g_signal_connect_data")); } //============================================================================== DynamicLibrary gtkLib { "libgtk-3.so" }, webkitLib { "libwebkit2gtk-4.0.so" }; const bool webKitIsAvailable = loadWebkitSymbols() && loadGtkSymbols(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebKitSymbols) }; JUCE_IMPLEMENT_SINGLETON (WebKitSymbols) //============================================================================== extern int juce_gtkWebkitMain (int argc, const char* argv[]); class CommandReceiver { public: struct Responder { virtual ~Responder() {} virtual void handleCommand (const String& cmd, const var& param) = 0; virtual void receiverHadError() = 0; }; CommandReceiver (Responder* responderToUse, int inputChannelToUse) : responder (responderToUse), inChannel (inputChannelToUse) { setBlocking (inChannel, false); } static void setBlocking (int fd, bool shouldBlock) { auto flags = fcntl (fd, F_GETFL); fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK))); } int getFd() const { return inChannel; } void tryNextRead() { for (;;) { auto len = (receivingLength ? sizeof (size_t) : bufferLength.len); if (! receivingLength) buffer.realloc (len); auto* dst = (receivingLength ? bufferLength.data : buffer.getData()); auto actual = read (inChannel, &dst[pos], static_cast (len - pos)); if (actual < 0) { if (errno == EINTR) continue; break; } pos += static_cast (actual); if (pos == len) { pos = 0; if (! receivingLength) parseJSON (String (buffer.getData(), bufferLength.len)); receivingLength = (! receivingLength); } } if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr) responder->receiverHadError(); } static void sendCommand (int outChannel, const String& cmd, const var& params) { DynamicObject::Ptr obj = new DynamicObject; obj->setProperty (getCmdIdentifier(), cmd); if (! params.isVoid()) obj->setProperty (getParamIdentifier(), params); auto json = JSON::toString (var (obj.get())); auto jsonLength = static_cast (json.length()); auto len = sizeof (size_t) + jsonLength; HeapBlock buffer (len); auto* dst = buffer.getData(); memcpy (dst, &jsonLength, sizeof (size_t)); dst += sizeof (size_t); memcpy (dst, json.toRawUTF8(), jsonLength); ssize_t ret; for (;;) { ret = write (outChannel, buffer.getData(), len); if (ret != -1 || errno != EINTR) break; } } private: void parseJSON (const String& json) { auto object = JSON::fromString (json); if (! object.isVoid()) { auto cmd = object.getProperty (getCmdIdentifier(), {}).toString(); auto params = object.getProperty (getParamIdentifier(), {}); if (responder != nullptr) responder->handleCommand (cmd, params); } } static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; } static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; } Responder* responder = nullptr; int inChannel = 0; size_t pos = 0; bool receivingLength = true; union { char data [sizeof (size_t)]; size_t len; } bufferLength; HeapBlock buffer; }; #define juce_g_signal_connect(instance, detailed_signal, c_handler, data) \ WebKitSymbols::getInstance()->juce_g_signal_connect_data (instance, detailed_signal, c_handler, data, nullptr, (GConnectFlags) 0) //============================================================================== class GtkChildProcess : private CommandReceiver::Responder { public: //============================================================================== GtkChildProcess (int inChannel, int outChannelToUse) : outChannel (outChannelToUse), receiver (this, inChannel) {} int entry() { CommandReceiver::setBlocking (outChannel, true); WebKitSymbols::getInstance()->juce_gtk_init (nullptr, nullptr); auto* settings = WebKitSymbols::getInstance()->juce_webkit_settings_new(); WebKitSymbols::getInstance()->juce_webkit_settings_set_hardware_acceleration_policy (settings, /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */ 2); auto* plug = WebKitSymbols::getInstance()->juce_gtk_plug_new (0); auto* container = WebKitSymbols::getInstance()->juce_gtk_scrolled_window_new (nullptr, nullptr); auto* webviewWidget = WebKitSymbols::getInstance()->juce_webkit_web_view_new_with_settings (settings); webview = (WebKitWebView*) webviewWidget; WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) container, webviewWidget); WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) plug, container); WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, "about:blank"); juce_g_signal_connect (webview, "decide-policy", (GCallback) decidePolicyCallback, this); juce_g_signal_connect (webview, "load-changed", (GCallback) loadChangedCallback, this); juce_g_signal_connect (webview, "load-failed", (GCallback) loadFailedCallback, this); WebKitSymbols::getInstance()->juce_gtk_widget_show_all (plug); auto wID = (unsigned long) WebKitSymbols::getInstance()->juce_gtk_plug_get_id ((GtkPlug*) plug); ssize_t ret; for (;;) { ret = write (outChannel, &wID, sizeof (wID)); if (ret != -1 || errno != EINTR) break; } WebKitSymbols::getInstance()->juce_g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this); receiver.tryNextRead(); WebKitSymbols::getInstance()->juce_gtk_main(); WebKitSymbols::getInstance()->deleteInstance(); return 0; } void goToURL (const var& params) { static Identifier urlIdentifier ("url"); auto url = params.getProperty (urlIdentifier, var()).toString(); WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, url.toRawUTF8()); } void handleDecisionResponse (const var& params) { auto* decision = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0))); bool allow = params.getProperty ("allow", var (false)); if (decision != nullptr && decisions.contains (decision)) { if (allow) WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision); else WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision); decisions.removeAllInstancesOf (decision); WebKitSymbols::getInstance()->juce_g_object_unref (decision); } } //============================================================================== void handleCommand (const String& cmd, const var& params) override { if (cmd == "quit") quit(); else if (cmd == "goToURL") goToURL (params); else if (cmd == "goBack") WebKitSymbols::getInstance()->juce_webkit_web_view_go_back (webview); else if (cmd == "goForward") WebKitSymbols::getInstance()->juce_webkit_web_view_go_forward (webview); else if (cmd == "refresh") WebKitSymbols::getInstance()->juce_webkit_web_view_reload (webview); else if (cmd == "stop") WebKitSymbols::getInstance()->juce_webkit_web_view_stop_loading (webview); else if (cmd == "decision") handleDecisionResponse (params); } void receiverHadError() override { exit (-1); } //============================================================================== bool pipeReady (gint fd, GIOCondition) { if (fd == receiver.getFd()) { receiver.tryNextRead(); return true; } return false; } void quit() { WebKitSymbols::getInstance()->juce_gtk_main_quit(); } String getURIStringForAction (WebKitNavigationAction* action) { auto* request = WebKitSymbols::getInstance()->juce_webkit_navigation_action_get_request (action); return WebKitSymbols::getInstance()->juce_webkit_uri_request_get_uri (request); } bool onNavigation (String frameName, WebKitNavigationAction* action, WebKitPolicyDecision* decision) { if (decision != nullptr && frameName.isEmpty()) { WebKitSymbols::getInstance()->juce_g_object_ref (decision); decisions.add (decision); DynamicObject::Ptr params = new DynamicObject; params->setProperty ("url", getURIStringForAction (action)); params->setProperty ("decision_id", (int64) decision); CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params.get())); return true; } return false; } bool onNewWindow (String /*frameName*/, WebKitNavigationAction* action, WebKitPolicyDecision* decision) { if (decision != nullptr) { DynamicObject::Ptr params = new DynamicObject; params->setProperty ("url", getURIStringForAction (action)); CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params.get())); // never allow new windows WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision); return true; } return false; } void onLoadChanged (WebKitLoadEvent loadEvent) { if (loadEvent == WEBKIT_LOAD_FINISHED) { DynamicObject::Ptr params = new DynamicObject; params->setProperty ("url", String (WebKitSymbols::getInstance()->juce_webkit_web_view_get_uri (webview))); CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params.get())); } } bool onDecidePolicy (WebKitPolicyDecision* decision, WebKitPolicyDecisionType decisionType) { switch (decisionType) { case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: { auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision; auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision); return onNavigation (String (frameName != nullptr ? frameName : ""), WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision), decision); } break; case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: { auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision; auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision); return onNewWindow (String (frameName != nullptr ? frameName : ""), WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision), decision); } break; case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: { auto* response = (WebKitNavigationPolicyDecision*) decision; // for now just always allow response requests ignoreUnused (response); WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision); return true; } break; default: break; } return false; } void onLoadFailed (GError* error) { DynamicObject::Ptr params = new DynamicObject; params->setProperty ("error", String (error != nullptr ? error->message : "unknown error")); CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params.get())); } private: static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user) { return (reinterpret_cast (user)->pipeReady (fd, condition) ? TRUE : FALSE); } static gboolean decidePolicyCallback (WebKitWebView*, WebKitPolicyDecision* decision, WebKitPolicyDecisionType decisionType, gpointer user) { auto& owner = *reinterpret_cast (user); return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE); } static void loadChangedCallback (WebKitWebView*, WebKitLoadEvent loadEvent, gpointer user) { auto& owner = *reinterpret_cast (user); owner.onLoadChanged (loadEvent); } static void loadFailedCallback (WebKitWebView*, WebKitLoadEvent /*loadEvent*/, gchar* /*failing_uri*/, GError* error, gpointer user) { auto& owner = *reinterpret_cast (user); owner.onLoadFailed (error); } int outChannel = 0; CommandReceiver receiver; WebKitWebView* webview = nullptr; Array decisions; }; //============================================================================== class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder { public: Pimpl (WebBrowserComponent& parent) : Thread ("Webview"), owner (parent) { webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable(); } ~Pimpl() override { quit(); } //============================================================================== void init() { if (! webKitIsAvailable) return; launchChild(); auto ret = pipe (threadControl); ignoreUnused (ret); jassert (ret == 0); CommandReceiver::setBlocking (inChannel, true); CommandReceiver::setBlocking (outChannel, true); CommandReceiver::setBlocking (threadControl[0], false); CommandReceiver::setBlocking (threadControl[1], true); unsigned long windowHandle; auto actual = read (inChannel, &windowHandle, sizeof (windowHandle)); if (actual != (ssize_t) sizeof (windowHandle)) { killChild(); return; } receiver.reset (new CommandReceiver (this, inChannel)); pfds.push_back ({ threadControl[0], POLLIN, 0 }); pfds.push_back ({ receiver->getFd(), POLLIN, 0 }); startThread(); xembed.reset (new XEmbedComponent (windowHandle)); owner.addAndMakeVisible (xembed.get()); } void quit() { if (! webKitIsAvailable) return; if (isThreadRunning()) { signalThreadShouldExit(); char ignore = 0; ssize_t ret; for (;;) { ret = write (threadControl[1], &ignore, 1); if (ret != -1 || errno != EINTR) break; } waitForThreadToExit (-1); receiver = nullptr; } if (childProcess != 0) { CommandReceiver::sendCommand (outChannel, "quit", {}); killChild(); } } //============================================================================== void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) { if (! webKitIsAvailable) return; DynamicObject::Ptr params = new DynamicObject; params->setProperty ("url", url); if (headers != nullptr) params->setProperty ("headers", var (*headers)); if (postData != nullptr) params->setProperty ("postData", var (*postData)); CommandReceiver::sendCommand (outChannel, "goToURL", var (params.get())); } void goBack() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack", {}); } void goForward() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); } void refresh() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh", {}); } void stop() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop", {}); } void resized() { if (xembed != nullptr) xembed->setBounds (owner.getLocalBounds()); } private: //============================================================================== void killChild() { if (childProcess != 0) { xembed = nullptr; int status = 0, result = 0; result = waitpid (childProcess, &status, WNOHANG); for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i) { Thread::sleep (100); result = waitpid (childProcess, &status, WNOHANG); } // clean-up any zombies status = 0; if (! WIFEXITED(status) || result != childProcess) { for (;;) { kill (childProcess, SIGTERM); waitpid (childProcess, &status, 0); if (WIFEXITED (status)) break; } } childProcess = 0; } } void launchChild() { int inPipe[2], outPipe[2]; auto ret = pipe (inPipe); ignoreUnused (ret); jassert (ret == 0); ret = pipe (outPipe); ignoreUnused (ret); jassert (ret == 0); auto pid = fork(); if (pid == 0) { close (inPipe[0]); close (outPipe[1]); HeapBlock argv (5); StringArray arguments; arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName()); arguments.add ("--juce-gtkwebkitfork-child"); arguments.add (String (outPipe[0])); arguments.add (String (inPipe [1])); for (int i = 0; i < arguments.size(); ++i) argv[i] = arguments[i].toRawUTF8(); argv[4] = nullptr; #if JUCE_STANDALONE_APPLICATION execv (arguments[0].toRawUTF8(), (char**) argv.getData()); #else juce_gtkWebkitMain (4, (const char**) argv.getData()); #endif exit (0); } close (inPipe[1]); close (outPipe[0]); inChannel = inPipe[0]; outChannel = outPipe[1]; childProcess = pid; } void run() override { while (! threadShouldExit()) { if (shouldExit()) return; receiver->tryNextRead(); int result = 0; while (result == 0 || (result < 0 && errno == EINTR)) result = poll (&pfds.front(), static_cast (pfds.size()), 0); if (result < 0) break; } } bool shouldExit() { char ignore; auto result = read (threadControl[0], &ignore, 1); return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)); } //============================================================================== void handleCommandOnMessageThread (const String& cmd, const var& params) { auto url = params.getProperty ("url", var()).toString(); if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params); else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url); else if (cmd == "windowCloseRequest") owner.windowCloseRequest(); else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url); else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params); threadBlocker.signal(); } void handlePageAboutToLoad (const String& url, const var& inputParams) { int64 decision_id = inputParams.getProperty ("decision_id", var (0)); if (decision_id != 0) { DynamicObject::Ptr params = new DynamicObject; params->setProperty ("decision_id", decision_id); params->setProperty ("allow", owner.pageAboutToLoad (url)); CommandReceiver::sendCommand (outChannel, "decision", var (params.get())); } } void handlePageLoadHadNetworkError (const var& params) { String error = params.getProperty ("error", "Unknown error"); if (owner.pageLoadHadNetworkError (error)) goToURL (String ("data:text/plain,") + error, nullptr, nullptr); } void handleCommand (const String& cmd, const var& params) override { threadBlocker.reset(); (new HandleOnMessageThread (this, cmd, params))->post(); // wait until the command has executed on the message thread // this ensures that Pimpl can never be deleted while the // message has not been executed yet threadBlocker.wait (-1); } void receiverHadError() override {} //============================================================================== struct HandleOnMessageThread : public CallbackMessage { HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params) : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params) {} void messageCallback() override { owner->handleCommandOnMessageThread (cmdToSend, paramsToSend); } Pimpl* owner = nullptr; String cmdToSend; var paramsToSend; }; bool webKitIsAvailable = false; WebBrowserComponent& owner; std::unique_ptr receiver; int childProcess = 0, inChannel = 0, outChannel = 0; int threadControl[2]; std::unique_ptr xembed; WaitableEvent threadBlocker; std::vector pfds; }; //============================================================================== WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) : browser (new Pimpl (*this)), unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) { ignoreUnused (blankPageShown); ignoreUnused (unloadPageWhenBrowserIsHidden); setOpaque (true); browser->init(); } WebBrowserComponent::~WebBrowserComponent() { } //============================================================================== void WebBrowserComponent::goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) { lastURL = url; if (headers != nullptr) lastHeaders = *headers; else lastHeaders.clear(); if (postData != nullptr) lastPostData = *postData; else lastPostData.reset(); browser->goToURL (url, headers, postData); } void WebBrowserComponent::stop() { browser->stop(); } void WebBrowserComponent::goBack() { lastURL.clear(); browser->goBack(); } void WebBrowserComponent::goForward() { lastURL.clear(); browser->goForward(); } void WebBrowserComponent::refresh() { browser->refresh(); } //============================================================================== void WebBrowserComponent::paint (Graphics& g) { g.fillAll (Colours::white); } void WebBrowserComponent::checkWindowAssociation() { } void WebBrowserComponent::reloadLastURL() { if (lastURL.isNotEmpty()) { goToURL (lastURL, &lastHeaders, &lastPostData); lastURL.clear(); } } void WebBrowserComponent::parentHierarchyChanged() { checkWindowAssociation(); } void WebBrowserComponent::resized() { if (browser != nullptr) browser->resized(); } void WebBrowserComponent::visibilityChanged() { checkWindowAssociation(); } void WebBrowserComponent::focusGained (FocusChangeType) { } void WebBrowserComponent::clearCookies() { // Currently not implemented on linux as WebBrowserComponent currently does not // store cookies on linux jassertfalse; } int juce_gtkWebkitMain (int argc, const char* argv[]) { if (argc != 4) return -1; GtkChildProcess child (String (argv[2]).getIntValue(), String (argv[3]).getIntValue()); return child.entry(); } } // namespace juce