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.

1025 lines
37KB

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