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.

1052 lines
38KB

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