/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). 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() const noexcept { 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 unloadWhenHidden) : browser (new Pimpl (*this)), unloadPageWhenHidden (unloadWhenHidden) { ignoreUnused (blankPageShown); ignoreUnused (unloadPageWhenHidden); 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