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.

1050 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. [[maybe_unused]] auto* response = (WebKitNavigationPolicyDecision*) decision;
  429. // for now just always allow response requests
  430. WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
  431. return true;
  432. }
  433. break;
  434. default:
  435. break;
  436. }
  437. return false;
  438. }
  439. void onLoadFailed (GError* error)
  440. {
  441. DynamicObject::Ptr params = new DynamicObject;
  442. params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
  443. CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params.get()));
  444. }
  445. private:
  446. static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
  447. {
  448. return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
  449. }
  450. static gboolean decidePolicyCallback (WebKitWebView*,
  451. WebKitPolicyDecision* decision,
  452. WebKitPolicyDecisionType decisionType,
  453. gpointer user)
  454. {
  455. auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
  456. return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
  457. }
  458. static void loadChangedCallback (WebKitWebView*,
  459. WebKitLoadEvent loadEvent,
  460. gpointer user)
  461. {
  462. auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
  463. owner.onLoadChanged (loadEvent);
  464. }
  465. static void loadFailedCallback (WebKitWebView*,
  466. WebKitLoadEvent /*loadEvent*/,
  467. gchar* /*failing_uri*/,
  468. GError* error,
  469. gpointer user)
  470. {
  471. auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
  472. owner.onLoadFailed (error);
  473. }
  474. int outChannel = 0;
  475. CommandReceiver receiver;
  476. String userAgent;
  477. WebKitWebView* webview = nullptr;
  478. Array<WebKitPolicyDecision*> decisions;
  479. };
  480. //==============================================================================
  481. class WebBrowserComponent::Pimpl : private Thread,
  482. private CommandReceiver::Responder
  483. {
  484. public:
  485. Pimpl (WebBrowserComponent& parent, const String& userAgentToUse)
  486. : Thread ("Webview"), owner (parent), userAgent (userAgentToUse)
  487. {
  488. webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable();
  489. }
  490. ~Pimpl() override
  491. {
  492. quit();
  493. }
  494. //==============================================================================
  495. void init()
  496. {
  497. if (! webKitIsAvailable)
  498. return;
  499. launchChild();
  500. [[maybe_unused]] auto ret = pipe (threadControl);
  501. jassert (ret == 0);
  502. CommandReceiver::setBlocking (inChannel, true);
  503. CommandReceiver::setBlocking (outChannel, true);
  504. CommandReceiver::setBlocking (threadControl[0], false);
  505. CommandReceiver::setBlocking (threadControl[1], true);
  506. unsigned long windowHandle;
  507. auto actual = read (inChannel, &windowHandle, sizeof (windowHandle));
  508. if (actual != (ssize_t) sizeof (windowHandle))
  509. {
  510. killChild();
  511. return;
  512. }
  513. receiver.reset (new CommandReceiver (this, inChannel));
  514. pfds.push_back ({ threadControl[0], POLLIN, 0 });
  515. pfds.push_back ({ receiver->getFd(), POLLIN, 0 });
  516. startThread();
  517. xembed.reset (new XEmbedComponent (windowHandle));
  518. owner.addAndMakeVisible (xembed.get());
  519. }
  520. void quit()
  521. {
  522. if (! webKitIsAvailable)
  523. return;
  524. if (isThreadRunning())
  525. {
  526. signalThreadShouldExit();
  527. char ignore = 0;
  528. ssize_t ret;
  529. for (;;)
  530. {
  531. ret = write (threadControl[1], &ignore, 1);
  532. if (ret != -1 || errno != EINTR)
  533. break;
  534. }
  535. waitForThreadToExit (-1);
  536. receiver = nullptr;
  537. }
  538. if (childProcess != 0)
  539. {
  540. CommandReceiver::sendCommand (outChannel, "quit", {});
  541. killChild();
  542. }
  543. }
  544. //==============================================================================
  545. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
  546. {
  547. if (! webKitIsAvailable)
  548. return;
  549. DynamicObject::Ptr params = new DynamicObject;
  550. params->setProperty ("url", url);
  551. if (headers != nullptr)
  552. params->setProperty ("headers", var (*headers));
  553. if (postData != nullptr)
  554. params->setProperty ("postData", var (*postData));
  555. CommandReceiver::sendCommand (outChannel, "goToURL", var (params.get()));
  556. }
  557. void goBack() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack", {}); }
  558. void goForward() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); }
  559. void refresh() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh", {}); }
  560. void stop() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop", {}); }
  561. void resized()
  562. {
  563. if (xembed != nullptr)
  564. xembed->setBounds (owner.getLocalBounds());
  565. }
  566. private:
  567. //==============================================================================
  568. void killChild()
  569. {
  570. if (childProcess != 0)
  571. {
  572. xembed = nullptr;
  573. int status = 0, result = 0;
  574. result = waitpid (childProcess, &status, WNOHANG);
  575. for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
  576. {
  577. Thread::sleep (100);
  578. result = waitpid (childProcess, &status, WNOHANG);
  579. }
  580. // clean-up any zombies
  581. status = 0;
  582. if (! WIFEXITED(status) || result != childProcess)
  583. {
  584. for (;;)
  585. {
  586. kill (childProcess, SIGTERM);
  587. waitpid (childProcess, &status, 0);
  588. if (WIFEXITED (status))
  589. break;
  590. }
  591. }
  592. childProcess = 0;
  593. }
  594. }
  595. void launchChild()
  596. {
  597. int inPipe[2], outPipe[2];
  598. [[maybe_unused]] auto ret = pipe (inPipe);
  599. jassert (ret == 0);
  600. ret = pipe (outPipe);
  601. jassert (ret == 0);
  602. auto pid = fork();
  603. if (pid == 0)
  604. {
  605. close (inPipe[0]);
  606. close (outPipe[1]);
  607. StringArray arguments;
  608. arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
  609. arguments.add ("--juce-gtkwebkitfork-child");
  610. arguments.add (String (outPipe[0]));
  611. arguments.add (String (inPipe [1]));
  612. if (userAgent.isNotEmpty())
  613. arguments.add (userAgent);
  614. std::vector<const char*> argv;
  615. argv.reserve (static_cast<std::size_t> (arguments.size() + 1));
  616. for (const auto& arg : arguments)
  617. argv.push_back (arg.toRawUTF8());
  618. argv.push_back (nullptr);
  619. if (JUCEApplicationBase::isStandaloneApp())
  620. execv (arguments[0].toRawUTF8(), (char**) argv.data());
  621. else
  622. juce_gtkWebkitMain (arguments.size(), (const char**) argv.data());
  623. exit (0);
  624. }
  625. close (inPipe[1]);
  626. close (outPipe[0]);
  627. inChannel = inPipe[0];
  628. outChannel = outPipe[1];
  629. childProcess = pid;
  630. }
  631. void run() override
  632. {
  633. while (! threadShouldExit())
  634. {
  635. if (shouldExit())
  636. return;
  637. receiver->tryNextRead();
  638. int result = 0;
  639. while (result == 0 || (result < 0 && errno == EINTR))
  640. result = poll (&pfds.front(), static_cast<nfds_t> (pfds.size()), 0);
  641. if (result < 0)
  642. break;
  643. }
  644. }
  645. bool shouldExit()
  646. {
  647. char ignore;
  648. auto result = read (threadControl[0], &ignore, 1);
  649. return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
  650. }
  651. //==============================================================================
  652. void handleCommandOnMessageThread (const String& cmd, const var& params)
  653. {
  654. auto url = params.getProperty ("url", var()).toString();
  655. if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
  656. else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
  657. else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
  658. else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
  659. else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
  660. threadBlocker.signal();
  661. }
  662. void handlePageAboutToLoad (const String& url, const var& inputParams)
  663. {
  664. int64 decision_id = inputParams.getProperty ("decision_id", var (0));
  665. if (decision_id != 0)
  666. {
  667. DynamicObject::Ptr params = new DynamicObject;
  668. params->setProperty ("decision_id", decision_id);
  669. params->setProperty ("allow", owner.pageAboutToLoad (url));
  670. CommandReceiver::sendCommand (outChannel, "decision", var (params.get()));
  671. }
  672. }
  673. void handlePageLoadHadNetworkError (const var& params)
  674. {
  675. String error = params.getProperty ("error", "Unknown error");
  676. if (owner.pageLoadHadNetworkError (error))
  677. goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
  678. }
  679. void handleCommand (const String& cmd, const var& params) override
  680. {
  681. threadBlocker.reset();
  682. (new HandleOnMessageThread (this, cmd, params))->post();
  683. // wait until the command has executed on the message thread
  684. // this ensures that Pimpl can never be deleted while the
  685. // message has not been executed yet
  686. threadBlocker.wait (-1);
  687. }
  688. void receiverHadError() override {}
  689. //==============================================================================
  690. struct HandleOnMessageThread : public CallbackMessage
  691. {
  692. HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
  693. : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
  694. {}
  695. void messageCallback() override
  696. {
  697. owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
  698. }
  699. Pimpl* owner = nullptr;
  700. String cmdToSend;
  701. var paramsToSend;
  702. };
  703. bool webKitIsAvailable = false;
  704. WebBrowserComponent& owner;
  705. String userAgent;
  706. std::unique_ptr<CommandReceiver> receiver;
  707. int childProcess = 0, inChannel = 0, outChannel = 0;
  708. int threadControl[2];
  709. std::unique_ptr<XEmbedComponent> xembed;
  710. WaitableEvent threadBlocker;
  711. std::vector<pollfd> pfds;
  712. };
  713. //==============================================================================
  714. WebBrowserComponent::WebBrowserComponent (const Options& options)
  715. : browser (new Pimpl (*this, options.getUserAgent()))
  716. {
  717. ignoreUnused (blankPageShown);
  718. ignoreUnused (unloadPageWhenHidden);
  719. setOpaque (true);
  720. browser->init();
  721. }
  722. WebBrowserComponent::~WebBrowserComponent()
  723. {
  724. }
  725. //==============================================================================
  726. void WebBrowserComponent::goToURL (const String& url,
  727. const StringArray* headers,
  728. const MemoryBlock* postData)
  729. {
  730. lastURL = url;
  731. if (headers != nullptr)
  732. lastHeaders = *headers;
  733. else
  734. lastHeaders.clear();
  735. if (postData != nullptr)
  736. lastPostData = *postData;
  737. else
  738. lastPostData.reset();
  739. browser->goToURL (url, headers, postData);
  740. }
  741. void WebBrowserComponent::stop()
  742. {
  743. browser->stop();
  744. }
  745. void WebBrowserComponent::goBack()
  746. {
  747. lastURL.clear();
  748. browser->goBack();
  749. }
  750. void WebBrowserComponent::goForward()
  751. {
  752. lastURL.clear();
  753. browser->goForward();
  754. }
  755. void WebBrowserComponent::refresh()
  756. {
  757. browser->refresh();
  758. }
  759. //==============================================================================
  760. void WebBrowserComponent::paint (Graphics& g)
  761. {
  762. g.fillAll (Colours::white);
  763. }
  764. void WebBrowserComponent::checkWindowAssociation()
  765. {
  766. }
  767. void WebBrowserComponent::reloadLastURL()
  768. {
  769. if (lastURL.isNotEmpty())
  770. {
  771. goToURL (lastURL, &lastHeaders, &lastPostData);
  772. lastURL.clear();
  773. }
  774. }
  775. void WebBrowserComponent::parentHierarchyChanged()
  776. {
  777. checkWindowAssociation();
  778. }
  779. void WebBrowserComponent::resized()
  780. {
  781. if (browser != nullptr)
  782. browser->resized();
  783. }
  784. void WebBrowserComponent::visibilityChanged()
  785. {
  786. checkWindowAssociation();
  787. }
  788. void WebBrowserComponent::focusGained (FocusChangeType)
  789. {
  790. }
  791. void WebBrowserComponent::clearCookies()
  792. {
  793. // Currently not implemented on linux as WebBrowserComponent currently does not
  794. // store cookies on linux
  795. jassertfalse;
  796. }
  797. bool WebBrowserComponent::areOptionsSupported (const Options& options)
  798. {
  799. return (options.getBackend() == Options::Backend::defaultBackend);
  800. }
  801. int juce_gtkWebkitMain (int argc, const char* argv[])
  802. {
  803. if (argc < 4)
  804. return -1;
  805. GtkChildProcess child (String (argv[2]).getIntValue(),
  806. String (argv[3]).getIntValue(),
  807. argc >= 5 ? String (argv[4]) : String());
  808. return child.entry();
  809. }
  810. } // namespace juce