The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1068 lines
40KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
  19. #include "juce_LinuxSubprocessHelperBinaryData.h"
  20. #endif
  21. namespace juce
  22. {
  23. //==============================================================================
  24. class WebKitSymbols : public DeletedAtShutdown
  25. {
  26. public:
  27. //==============================================================================
  28. bool isWebKitAvailable() const noexcept { return webKitIsAvailable; }
  29. //==============================================================================
  30. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_new, juce_webkit_settings_new,
  31. (), WebKitSettings*)
  32. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_hardware_acceleration_policy, juce_webkit_settings_set_hardware_acceleration_policy,
  33. (WebKitSettings*, int), void)
  34. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_user_agent, juce_webkit_settings_set_user_agent,
  35. (WebKitSettings*, const gchar*), void)
  36. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_new_with_settings, juce_webkit_web_view_new_with_settings,
  37. (WebKitSettings*), GtkWidget*)
  38. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_load_uri, juce_webkit_web_view_load_uri,
  39. (WebKitWebView*, const gchar*), void)
  40. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_use, juce_webkit_policy_decision_use,
  41. (WebKitPolicyDecision*), void)
  42. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_ignore, juce_webkit_policy_decision_ignore,
  43. (WebKitPolicyDecision*), void)
  44. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_back, juce_webkit_web_view_go_back,
  45. (WebKitWebView*), void)
  46. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_forward, juce_webkit_web_view_go_forward,
  47. (WebKitWebView*), void)
  48. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_reload, juce_webkit_web_view_reload,
  49. (WebKitWebView*), void)
  50. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_stop_loading, juce_webkit_web_view_stop_loading,
  51. (WebKitWebView*), void)
  52. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_request_get_uri, juce_webkit_uri_request_get_uri,
  53. (WebKitURIRequest*), const gchar*)
  54. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_action_get_request, juce_webkit_navigation_action_get_request,
  55. (WebKitNavigationAction*), WebKitURIRequest*)
  56. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_frame_name, juce_webkit_navigation_policy_decision_get_frame_name,
  57. (WebKitNavigationPolicyDecision*), const gchar*)
  58. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_navigation_action, juce_webkit_navigation_policy_decision_get_navigation_action,
  59. (WebKitNavigationPolicyDecision*), WebKitNavigationAction*)
  60. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_uri, juce_webkit_web_view_get_uri,
  61. (WebKitWebView*), const gchar*)
  62. //==============================================================================
  63. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_init, juce_gtk_init,
  64. (int*, char***), void)
  65. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_new, juce_gtk_plug_new,
  66. (::Window), GtkWidget*)
  67. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_scrolled_window_new, juce_gtk_scrolled_window_new,
  68. (GtkAdjustment*, GtkAdjustment*), GtkWidget*)
  69. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_container_add, juce_gtk_container_add,
  70. (GtkContainer*, GtkWidget*), void)
  71. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_widget_show_all, juce_gtk_widget_show_all,
  72. (GtkWidget*), void)
  73. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_get_id, juce_gtk_plug_get_id,
  74. (GtkPlug*), ::Window)
  75. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main, juce_gtk_main,
  76. (), void)
  77. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main_quit, juce_gtk_main_quit,
  78. (), void)
  79. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_unix_fd_add, juce_g_unix_fd_add,
  80. (gint, GIOCondition, GUnixFDSourceFunc, gpointer), guint)
  81. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_ref, juce_g_object_ref,
  82. (gpointer), gpointer)
  83. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_unref, juce_g_object_unref,
  84. (gpointer), void)
  85. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_signal_connect_data, juce_g_signal_connect_data,
  86. (gpointer, const gchar*, GCallback, gpointer, GClosureNotify, GConnectFlags), gulong)
  87. //==============================================================================
  88. JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gdk_set_allowed_backends, juce_gdk_set_allowed_backends,
  89. (const char*), void)
  90. //==============================================================================
  91. JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WebKitSymbols)
  92. private:
  93. WebKitSymbols() = default;
  94. ~WebKitSymbols()
  95. {
  96. clearSingletonInstance();
  97. }
  98. template <typename FuncPtr>
  99. struct SymbolBinding
  100. {
  101. FuncPtr& func;
  102. const char* name;
  103. };
  104. template <typename FuncPtr>
  105. SymbolBinding<FuncPtr> makeSymbolBinding (FuncPtr& func, const char* name)
  106. {
  107. return { func, name };
  108. }
  109. template <typename FuncPtr>
  110. bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding)
  111. {
  112. if (auto* func = lib.getFunction (binding.name))
  113. {
  114. binding.func = reinterpret_cast<FuncPtr> (func);
  115. return true;
  116. }
  117. return false;
  118. }
  119. template <typename FuncPtr, typename... Args>
  120. bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding, Args... args)
  121. {
  122. return loadSymbols (lib, binding) && loadSymbols (lib, args...);
  123. }
  124. //==============================================================================
  125. bool loadWebkitSymbols()
  126. {
  127. return loadSymbols (webkitLib,
  128. makeSymbolBinding (juce_webkit_settings_new, "webkit_settings_new"),
  129. makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy, "webkit_settings_set_hardware_acceleration_policy"),
  130. makeSymbolBinding (juce_webkit_settings_set_user_agent, "webkit_settings_set_user_agent"),
  131. makeSymbolBinding (juce_webkit_web_view_new_with_settings, "webkit_web_view_new_with_settings"),
  132. makeSymbolBinding (juce_webkit_policy_decision_use, "webkit_policy_decision_use"),
  133. makeSymbolBinding (juce_webkit_policy_decision_ignore, "webkit_policy_decision_ignore"),
  134. makeSymbolBinding (juce_webkit_web_view_go_back, "webkit_web_view_go_back"),
  135. makeSymbolBinding (juce_webkit_web_view_go_forward, "webkit_web_view_go_forward"),
  136. makeSymbolBinding (juce_webkit_web_view_reload, "webkit_web_view_reload"),
  137. makeSymbolBinding (juce_webkit_web_view_stop_loading, "webkit_web_view_stop_loading"),
  138. makeSymbolBinding (juce_webkit_uri_request_get_uri, "webkit_uri_request_get_uri"),
  139. makeSymbolBinding (juce_webkit_web_view_load_uri, "webkit_web_view_load_uri"),
  140. makeSymbolBinding (juce_webkit_navigation_action_get_request, "webkit_navigation_action_get_request"),
  141. makeSymbolBinding (juce_webkit_navigation_policy_decision_get_frame_name, "webkit_navigation_policy_decision_get_frame_name"),
  142. makeSymbolBinding (juce_webkit_navigation_policy_decision_get_navigation_action, "webkit_navigation_policy_decision_get_navigation_action"),
  143. makeSymbolBinding (juce_webkit_web_view_get_uri, "webkit_web_view_get_uri"));
  144. }
  145. bool loadGtkSymbols()
  146. {
  147. return loadSymbols (gtkLib,
  148. makeSymbolBinding (juce_gtk_init, "gtk_init"),
  149. makeSymbolBinding (juce_gtk_plug_new, "gtk_plug_new"),
  150. makeSymbolBinding (juce_gtk_scrolled_window_new, "gtk_scrolled_window_new"),
  151. makeSymbolBinding (juce_gtk_container_add, "gtk_container_add"),
  152. makeSymbolBinding (juce_gtk_widget_show_all, "gtk_widget_show_all"),
  153. makeSymbolBinding (juce_gtk_plug_get_id, "gtk_plug_get_id"),
  154. makeSymbolBinding (juce_gtk_main, "gtk_main"),
  155. makeSymbolBinding (juce_gtk_main_quit, "gtk_main_quit"),
  156. makeSymbolBinding (juce_g_unix_fd_add, "g_unix_fd_add"),
  157. makeSymbolBinding (juce_g_object_ref, "g_object_ref"),
  158. makeSymbolBinding (juce_g_object_unref, "g_object_unref"),
  159. makeSymbolBinding (juce_g_signal_connect_data, "g_signal_connect_data"),
  160. makeSymbolBinding (juce_gdk_set_allowed_backends, "gdk_set_allowed_backends"));
  161. }
  162. //==============================================================================
  163. DynamicLibrary gtkLib { "libgtk-3.so" }, webkitLib { "libwebkit2gtk-4.0.so" };
  164. const bool webKitIsAvailable = loadWebkitSymbols() && loadGtkSymbols();
  165. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebKitSymbols)
  166. };
  167. JUCE_IMPLEMENT_SINGLETON (WebKitSymbols)
  168. //==============================================================================
  169. extern "C" int juce_gtkWebkitMain (int argc, const char* const* argv);
  170. class CommandReceiver
  171. {
  172. public:
  173. struct Responder
  174. {
  175. virtual ~Responder() {}
  176. virtual void handleCommand (const String& cmd, const var& param) = 0;
  177. virtual void receiverHadError() = 0;
  178. };
  179. CommandReceiver (Responder* responderToUse, int inputChannelToUse)
  180. : responder (responderToUse), inChannel (inputChannelToUse)
  181. {
  182. setBlocking (inChannel, false);
  183. }
  184. static void setBlocking (int fd, bool shouldBlock)
  185. {
  186. auto flags = fcntl (fd, F_GETFL);
  187. fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
  188. : (flags | O_NONBLOCK)));
  189. }
  190. int getFd() const { return inChannel; }
  191. void tryNextRead()
  192. {
  193. for (;;)
  194. {
  195. auto len = (receivingLength ? sizeof (size_t) : bufferLength.len);
  196. if (! receivingLength)
  197. buffer.realloc (len);
  198. auto* dst = (receivingLength ? bufferLength.data : buffer.getData());
  199. auto actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
  200. if (actual < 0)
  201. {
  202. if (errno == EINTR)
  203. continue;
  204. break;
  205. }
  206. pos += static_cast<size_t> (actual);
  207. if (pos == len)
  208. {
  209. pos = 0;
  210. if (! receivingLength)
  211. parseJSON (String (buffer.getData(), bufferLength.len));
  212. receivingLength = (! receivingLength);
  213. }
  214. }
  215. if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
  216. responder->receiverHadError();
  217. }
  218. static void sendCommand (int outChannel, const String& cmd, const var& params)
  219. {
  220. DynamicObject::Ptr obj = new DynamicObject;
  221. obj->setProperty (getCmdIdentifier(), cmd);
  222. if (! params.isVoid())
  223. obj->setProperty (getParamIdentifier(), params);
  224. auto json = JSON::toString (var (obj.get()));
  225. auto jsonLength = static_cast<size_t> (json.length());
  226. auto len = sizeof (size_t) + jsonLength;
  227. HeapBlock<char> buffer (len);
  228. auto* dst = buffer.getData();
  229. memcpy (dst, &jsonLength, sizeof (size_t));
  230. dst += sizeof (size_t);
  231. memcpy (dst, json.toRawUTF8(), jsonLength);
  232. ssize_t ret;
  233. for (;;)
  234. {
  235. ret = write (outChannel, buffer.getData(), len);
  236. if (ret != -1 || errno != EINTR)
  237. break;
  238. }
  239. }
  240. private:
  241. void parseJSON (const String& json)
  242. {
  243. auto object = JSON::fromString (json);
  244. if (! object.isVoid())
  245. {
  246. auto cmd = object.getProperty (getCmdIdentifier(), {}).toString();
  247. auto params = object.getProperty (getParamIdentifier(), {});
  248. if (responder != nullptr)
  249. responder->handleCommand (cmd, params);
  250. }
  251. }
  252. static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; }
  253. static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; }
  254. Responder* responder = nullptr;
  255. int inChannel = 0;
  256. size_t pos = 0;
  257. bool receivingLength = true;
  258. union { char data [sizeof (size_t)]; size_t len; } bufferLength;
  259. HeapBlock<char> buffer;
  260. };
  261. #define juce_g_signal_connect(instance, detailed_signal, c_handler, data) \
  262. WebKitSymbols::getInstance()->juce_g_signal_connect_data (instance, detailed_signal, c_handler, data, nullptr, (GConnectFlags) 0)
  263. //==============================================================================
  264. class GtkChildProcess : private CommandReceiver::Responder
  265. {
  266. public:
  267. //==============================================================================
  268. GtkChildProcess (int inChannel, int outChannelToUse, const String& userAgentToUse)
  269. : outChannel (outChannelToUse),
  270. receiver (this, inChannel),
  271. userAgent (userAgentToUse)
  272. {}
  273. int entry()
  274. {
  275. CommandReceiver::setBlocking (outChannel, true);
  276. // webkit2gtk crashes when using the wayland backend embedded into an x11 window
  277. WebKitSymbols::getInstance()->juce_gdk_set_allowed_backends ("x11");
  278. WebKitSymbols::getInstance()->juce_gtk_init (nullptr, nullptr);
  279. auto* settings = WebKitSymbols::getInstance()->juce_webkit_settings_new();
  280. WebKitSymbols::getInstance()->juce_webkit_settings_set_hardware_acceleration_policy (settings,
  281. /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */ 2);
  282. if (userAgent.isNotEmpty())
  283. WebKitSymbols::getInstance()->juce_webkit_settings_set_user_agent (settings, userAgent.toRawUTF8());
  284. auto* plug = WebKitSymbols::getInstance()->juce_gtk_plug_new (0);
  285. auto* container = WebKitSymbols::getInstance()->juce_gtk_scrolled_window_new (nullptr, nullptr);
  286. auto* webviewWidget = WebKitSymbols::getInstance()->juce_webkit_web_view_new_with_settings (settings);
  287. webview = (WebKitWebView*) webviewWidget;
  288. WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) container, webviewWidget);
  289. WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) plug, container);
  290. WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, "about:blank");
  291. juce_g_signal_connect (webview, "decide-policy",
  292. (GCallback) decidePolicyCallback, this);
  293. juce_g_signal_connect (webview, "load-changed",
  294. (GCallback) loadChangedCallback, this);
  295. juce_g_signal_connect (webview, "load-failed",
  296. (GCallback) loadFailedCallback, this);
  297. WebKitSymbols::getInstance()->juce_gtk_widget_show_all (plug);
  298. auto wID = (unsigned long) WebKitSymbols::getInstance()->juce_gtk_plug_get_id ((GtkPlug*) plug);
  299. ssize_t ret;
  300. for (;;)
  301. {
  302. ret = write (outChannel, &wID, sizeof (wID));
  303. if (ret != -1 || errno != EINTR)
  304. break;
  305. }
  306. WebKitSymbols::getInstance()->juce_g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
  307. receiver.tryNextRead();
  308. WebKitSymbols::getInstance()->juce_gtk_main();
  309. WebKitSymbols::getInstance()->deleteInstance();
  310. return 0;
  311. }
  312. void goToURL (const var& params)
  313. {
  314. static Identifier urlIdentifier ("url");
  315. auto url = params.getProperty (urlIdentifier, var()).toString();
  316. WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, url.toRawUTF8());
  317. }
  318. void handleDecisionResponse (const var& params)
  319. {
  320. auto* decision = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
  321. bool allow = params.getProperty ("allow", var (false));
  322. if (decision != nullptr && decisions.contains (decision))
  323. {
  324. if (allow)
  325. WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
  326. else
  327. WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision);
  328. decisions.removeAllInstancesOf (decision);
  329. WebKitSymbols::getInstance()->juce_g_object_unref (decision);
  330. }
  331. }
  332. //==============================================================================
  333. void handleCommand (const String& cmd, const var& params) override
  334. {
  335. if (cmd == "quit") quit();
  336. else if (cmd == "goToURL") goToURL (params);
  337. else if (cmd == "goBack") WebKitSymbols::getInstance()->juce_webkit_web_view_go_back (webview);
  338. else if (cmd == "goForward") WebKitSymbols::getInstance()->juce_webkit_web_view_go_forward (webview);
  339. else if (cmd == "refresh") WebKitSymbols::getInstance()->juce_webkit_web_view_reload (webview);
  340. else if (cmd == "stop") WebKitSymbols::getInstance()->juce_webkit_web_view_stop_loading (webview);
  341. else if (cmd == "decision") handleDecisionResponse (params);
  342. }
  343. void receiverHadError() override
  344. {
  345. exit (-1);
  346. }
  347. //==============================================================================
  348. bool pipeReady (gint fd, GIOCondition)
  349. {
  350. if (fd == receiver.getFd())
  351. {
  352. receiver.tryNextRead();
  353. return true;
  354. }
  355. return false;
  356. }
  357. void quit()
  358. {
  359. WebKitSymbols::getInstance()->juce_gtk_main_quit();
  360. }
  361. String getURIStringForAction (WebKitNavigationAction* action)
  362. {
  363. auto* request = WebKitSymbols::getInstance()->juce_webkit_navigation_action_get_request (action);
  364. return WebKitSymbols::getInstance()->juce_webkit_uri_request_get_uri (request);
  365. }
  366. bool onNavigation (String frameName,
  367. WebKitNavigationAction* action,
  368. WebKitPolicyDecision* decision)
  369. {
  370. if (decision != nullptr && frameName.isEmpty())
  371. {
  372. WebKitSymbols::getInstance()->juce_g_object_ref (decision);
  373. decisions.add (decision);
  374. DynamicObject::Ptr params = new DynamicObject;
  375. params->setProperty ("url", getURIStringForAction (action));
  376. params->setProperty ("decision_id", (int64) decision);
  377. CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params.get()));
  378. return true;
  379. }
  380. return false;
  381. }
  382. bool onNewWindow (String /*frameName*/,
  383. WebKitNavigationAction* action,
  384. WebKitPolicyDecision* decision)
  385. {
  386. if (decision != nullptr)
  387. {
  388. DynamicObject::Ptr params = new DynamicObject;
  389. params->setProperty ("url", getURIStringForAction (action));
  390. CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params.get()));
  391. // never allow new windows
  392. WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision);
  393. return true;
  394. }
  395. return false;
  396. }
  397. void onLoadChanged (WebKitLoadEvent loadEvent)
  398. {
  399. if (loadEvent == WEBKIT_LOAD_FINISHED)
  400. {
  401. DynamicObject::Ptr params = new DynamicObject;
  402. params->setProperty ("url", String (WebKitSymbols::getInstance()->juce_webkit_web_view_get_uri (webview)));
  403. CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params.get()));
  404. }
  405. }
  406. bool onDecidePolicy (WebKitPolicyDecision* decision,
  407. WebKitPolicyDecisionType decisionType)
  408. {
  409. switch (decisionType)
  410. {
  411. case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
  412. {
  413. auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision;
  414. auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  415. return onNavigation (String (frameName != nullptr ? frameName : ""),
  416. WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  417. decision);
  418. }
  419. break;
  420. case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
  421. {
  422. auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision;
  423. auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  424. return onNewWindow (String (frameName != nullptr ? frameName : ""),
  425. WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  426. decision);
  427. }
  428. break;
  429. case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
  430. {
  431. [[maybe_unused]] auto* response = (WebKitNavigationPolicyDecision*) decision;
  432. // for now just always allow response requests
  433. WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
  434. return true;
  435. }
  436. break;
  437. default:
  438. break;
  439. }
  440. return false;
  441. }
  442. void onLoadFailed (GError* error)
  443. {
  444. DynamicObject::Ptr params = new DynamicObject;
  445. params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
  446. CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params.get()));
  447. }
  448. private:
  449. static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
  450. {
  451. return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
  452. }
  453. static gboolean decidePolicyCallback (WebKitWebView*,
  454. WebKitPolicyDecision* decision,
  455. WebKitPolicyDecisionType decisionType,
  456. gpointer user)
  457. {
  458. auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
  459. return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
  460. }
  461. static void loadChangedCallback (WebKitWebView*,
  462. WebKitLoadEvent loadEvent,
  463. gpointer user)
  464. {
  465. auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
  466. owner.onLoadChanged (loadEvent);
  467. }
  468. static void loadFailedCallback (WebKitWebView*,
  469. WebKitLoadEvent /*loadEvent*/,
  470. gchar* /*failing_uri*/,
  471. GError* error,
  472. gpointer user)
  473. {
  474. auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
  475. owner.onLoadFailed (error);
  476. }
  477. int outChannel = 0;
  478. CommandReceiver receiver;
  479. String userAgent;
  480. WebKitWebView* webview = nullptr;
  481. Array<WebKitPolicyDecision*> decisions;
  482. };
  483. //==============================================================================
  484. class WebBrowserComponent::Pimpl : private Thread,
  485. private CommandReceiver::Responder
  486. {
  487. public:
  488. Pimpl (WebBrowserComponent& parent, const String& userAgentToUse)
  489. : Thread ("Webview"), owner (parent), userAgent (userAgentToUse)
  490. {
  491. webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable();
  492. }
  493. ~Pimpl() override
  494. {
  495. quit();
  496. }
  497. //==============================================================================
  498. void init()
  499. {
  500. if (! webKitIsAvailable)
  501. return;
  502. launchChild();
  503. [[maybe_unused]] auto ret = pipe (threadControl);
  504. jassert (ret == 0);
  505. CommandReceiver::setBlocking (inChannel, true);
  506. CommandReceiver::setBlocking (outChannel, true);
  507. CommandReceiver::setBlocking (threadControl[0], false);
  508. CommandReceiver::setBlocking (threadControl[1], true);
  509. unsigned long windowHandle;
  510. auto actual = read (inChannel, &windowHandle, sizeof (windowHandle));
  511. if (actual != (ssize_t) sizeof (windowHandle))
  512. {
  513. killChild();
  514. return;
  515. }
  516. receiver.reset (new CommandReceiver (this, inChannel));
  517. pfds.push_back ({ threadControl[0], POLLIN, 0 });
  518. pfds.push_back ({ receiver->getFd(), POLLIN, 0 });
  519. startThread();
  520. xembed.reset (new XEmbedComponent (windowHandle));
  521. owner.addAndMakeVisible (xembed.get());
  522. }
  523. void quit()
  524. {
  525. if (! webKitIsAvailable)
  526. return;
  527. if (isThreadRunning())
  528. {
  529. signalThreadShouldExit();
  530. char ignore = 0;
  531. ssize_t ret;
  532. for (;;)
  533. {
  534. ret = write (threadControl[1], &ignore, 1);
  535. if (ret != -1 || errno != EINTR)
  536. break;
  537. }
  538. waitForThreadToExit (-1);
  539. receiver = nullptr;
  540. }
  541. if (childProcess != 0)
  542. {
  543. CommandReceiver::sendCommand (outChannel, "quit", {});
  544. killChild();
  545. }
  546. }
  547. //==============================================================================
  548. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
  549. {
  550. if (! webKitIsAvailable)
  551. return;
  552. DynamicObject::Ptr params = new DynamicObject;
  553. params->setProperty ("url", url);
  554. if (headers != nullptr)
  555. params->setProperty ("headers", var (*headers));
  556. if (postData != nullptr)
  557. params->setProperty ("postData", var (*postData));
  558. CommandReceiver::sendCommand (outChannel, "goToURL", var (params.get()));
  559. }
  560. void goBack() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack", {}); }
  561. void goForward() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); }
  562. void refresh() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh", {}); }
  563. void stop() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop", {}); }
  564. void resized()
  565. {
  566. if (xembed != nullptr)
  567. xembed->setBounds (owner.getLocalBounds());
  568. }
  569. private:
  570. //==============================================================================
  571. void killChild()
  572. {
  573. if (childProcess != 0)
  574. {
  575. xembed = nullptr;
  576. int status = 0, result = 0;
  577. result = waitpid (childProcess, &status, WNOHANG);
  578. for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
  579. {
  580. Thread::sleep (100);
  581. result = waitpid (childProcess, &status, WNOHANG);
  582. }
  583. // clean-up any zombies
  584. status = 0;
  585. if (! WIFEXITED(status) || result != childProcess)
  586. {
  587. for (;;)
  588. {
  589. kill (childProcess, SIGTERM);
  590. waitpid (childProcess, &status, 0);
  591. if (WIFEXITED (status))
  592. break;
  593. }
  594. }
  595. childProcess = 0;
  596. }
  597. }
  598. void launchChild()
  599. {
  600. int inPipe[2], outPipe[2];
  601. [[maybe_unused]] auto ret = pipe (inPipe);
  602. jassert (ret == 0);
  603. ret = pipe (outPipe);
  604. jassert (ret == 0);
  605. std::vector<String> arguments;
  606. #if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
  607. if (! JUCEApplicationBase::isStandaloneApp())
  608. {
  609. subprocessFile.emplace ("_juce_linux_subprocess");
  610. const auto externalSubprocessAvailable = subprocessFile->getFile().replaceWithData (LinuxSubprocessHelperBinaryData::juce_linux_subprocess_helper,
  611. LinuxSubprocessHelperBinaryData::juce_linux_subprocess_helperSize)
  612. && subprocessFile->getFile().setExecutePermission (true);
  613. ignoreUnused (externalSubprocessAvailable);
  614. jassert (externalSubprocessAvailable);
  615. /* The external subprocess will load the .so specified as its first argument and execute
  616. the function specified by the second. The remaining arguments will be passed on to
  617. the function.
  618. */
  619. arguments.emplace_back (subprocessFile->getFile().getFullPathName());
  620. arguments.emplace_back (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
  621. arguments.emplace_back ("juce_gtkWebkitMain");
  622. }
  623. #endif
  624. arguments.emplace_back (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
  625. arguments.emplace_back ("--juce-gtkwebkitfork-child");
  626. arguments.emplace_back (outPipe[0]);
  627. arguments.emplace_back (inPipe [1]);
  628. if (userAgent.isNotEmpty())
  629. arguments.emplace_back (userAgent);
  630. std::vector<const char*> argv (arguments.size() + 1, nullptr);
  631. std::transform (arguments.begin(), arguments.end(), argv.begin(), [] (const auto& arg)
  632. {
  633. return arg.toRawUTF8();
  634. });
  635. auto pid = fork();
  636. if (pid == 0)
  637. {
  638. close (inPipe[0]);
  639. close (outPipe[1]);
  640. if (JUCEApplicationBase::isStandaloneApp())
  641. {
  642. execv (arguments[0].toRawUTF8(), (char**) argv.data());
  643. }
  644. else
  645. {
  646. #if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
  647. execv (arguments[0].toRawUTF8(), (char**) argv.data());
  648. #else
  649. // After a fork in a multithreaded program, the child can only safely call
  650. // async-signal-safe functions until it calls execv, but if we reached this point
  651. // then execv won't be called at all! The following call is unsafe, and is very
  652. // likely to lead to unexpected behaviour.
  653. jassertfalse;
  654. juce_gtkWebkitMain ((int) arguments.size(), argv.data());
  655. #endif
  656. }
  657. exit (0);
  658. }
  659. close (inPipe[1]);
  660. close (outPipe[0]);
  661. inChannel = inPipe[0];
  662. outChannel = outPipe[1];
  663. childProcess = pid;
  664. }
  665. void run() override
  666. {
  667. while (! threadShouldExit())
  668. {
  669. if (shouldExit())
  670. return;
  671. receiver->tryNextRead();
  672. int result = 0;
  673. while (result == 0 || (result < 0 && errno == EINTR))
  674. result = poll (&pfds.front(), static_cast<nfds_t> (pfds.size()), 10);
  675. if (result < 0)
  676. break;
  677. }
  678. }
  679. bool shouldExit()
  680. {
  681. char ignore;
  682. auto result = read (threadControl[0], &ignore, 1);
  683. return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
  684. }
  685. //==============================================================================
  686. void handleCommandOnMessageThread (const String& cmd, const var& params)
  687. {
  688. auto url = params.getProperty ("url", var()).toString();
  689. if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
  690. else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
  691. else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
  692. else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
  693. else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
  694. }
  695. void handlePageAboutToLoad (const String& url, const var& inputParams)
  696. {
  697. int64 decision_id = inputParams.getProperty ("decision_id", var (0));
  698. if (decision_id != 0)
  699. {
  700. DynamicObject::Ptr params = new DynamicObject;
  701. params->setProperty ("decision_id", decision_id);
  702. params->setProperty ("allow", owner.pageAboutToLoad (url));
  703. CommandReceiver::sendCommand (outChannel, "decision", var (params.get()));
  704. }
  705. }
  706. void handlePageLoadHadNetworkError (const var& params)
  707. {
  708. String error = params.getProperty ("error", "Unknown error");
  709. if (owner.pageLoadHadNetworkError (error))
  710. goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
  711. }
  712. void handleCommand (const String& cmd, const var& params) override
  713. {
  714. MessageManager::callAsync ([liveness = std::weak_ptr (livenessProbe), this, cmd, params]
  715. {
  716. if (liveness.lock() != nullptr)
  717. handleCommandOnMessageThread (cmd, params);
  718. });
  719. }
  720. void receiverHadError() override {}
  721. //==============================================================================
  722. bool webKitIsAvailable = false;
  723. WebBrowserComponent& owner;
  724. String userAgent;
  725. std::unique_ptr<CommandReceiver> receiver;
  726. int childProcess = 0, inChannel = 0, outChannel = 0;
  727. int threadControl[2];
  728. std::unique_ptr<XEmbedComponent> xembed;
  729. std::shared_ptr<int> livenessProbe = std::make_shared<int> (0);
  730. std::vector<pollfd> pfds;
  731. std::optional<TemporaryFile> subprocessFile;
  732. };
  733. //==============================================================================
  734. WebBrowserComponent::WebBrowserComponent (const Options& options)
  735. : browser (new Pimpl (*this, options.getUserAgent()))
  736. {
  737. ignoreUnused (blankPageShown);
  738. ignoreUnused (unloadPageWhenHidden);
  739. setOpaque (true);
  740. browser->init();
  741. }
  742. WebBrowserComponent::~WebBrowserComponent()
  743. {
  744. }
  745. //==============================================================================
  746. void WebBrowserComponent::goToURL (const String& url,
  747. const StringArray* headers,
  748. const MemoryBlock* postData)
  749. {
  750. lastURL = url;
  751. if (headers != nullptr)
  752. lastHeaders = *headers;
  753. else
  754. lastHeaders.clear();
  755. if (postData != nullptr)
  756. lastPostData = *postData;
  757. else
  758. lastPostData.reset();
  759. browser->goToURL (url, headers, postData);
  760. }
  761. void WebBrowserComponent::stop()
  762. {
  763. browser->stop();
  764. }
  765. void WebBrowserComponent::goBack()
  766. {
  767. lastURL.clear();
  768. browser->goBack();
  769. }
  770. void WebBrowserComponent::goForward()
  771. {
  772. lastURL.clear();
  773. browser->goForward();
  774. }
  775. void WebBrowserComponent::refresh()
  776. {
  777. browser->refresh();
  778. }
  779. //==============================================================================
  780. void WebBrowserComponent::paint (Graphics& g)
  781. {
  782. g.fillAll (Colours::white);
  783. }
  784. void WebBrowserComponent::checkWindowAssociation()
  785. {
  786. }
  787. void WebBrowserComponent::reloadLastURL()
  788. {
  789. if (lastURL.isNotEmpty())
  790. {
  791. goToURL (lastURL, &lastHeaders, &lastPostData);
  792. lastURL.clear();
  793. }
  794. }
  795. void WebBrowserComponent::parentHierarchyChanged()
  796. {
  797. checkWindowAssociation();
  798. }
  799. void WebBrowserComponent::resized()
  800. {
  801. if (browser != nullptr)
  802. browser->resized();
  803. }
  804. void WebBrowserComponent::visibilityChanged()
  805. {
  806. checkWindowAssociation();
  807. }
  808. void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
  809. {
  810. }
  811. void WebBrowserComponent::clearCookies()
  812. {
  813. // Currently not implemented on linux as WebBrowserComponent currently does not
  814. // store cookies on linux
  815. jassertfalse;
  816. }
  817. bool WebBrowserComponent::areOptionsSupported (const Options& options)
  818. {
  819. return (options.getBackend() == Options::Backend::defaultBackend);
  820. }
  821. extern "C" __attribute__ ((visibility ("default"))) int juce_gtkWebkitMain (int argc, const char* const* argv)
  822. {
  823. if (argc < 4)
  824. return -1;
  825. GtkChildProcess child (String (argv[2]).getIntValue(),
  826. String (argv[3]).getIntValue(),
  827. argc >= 5 ? String (argv[4]) : String());
  828. return child.entry();
  829. }
  830. } // namespace juce