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.

700 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. class CommandReceiver
  18. {
  19. public:
  20. struct Responder
  21. {
  22. virtual ~Responder() {}
  23. virtual void handleCommand (const String& cmd, const var& param) = 0;
  24. virtual void receiverHadError() = 0;
  25. };
  26. CommandReceiver (Responder* responderToUse, int inputChannelToUse)
  27. : responder (responderToUse), inChannel (inputChannelToUse)
  28. {
  29. setBlocking (inChannel, false);
  30. }
  31. static void setBlocking (int fd, bool shouldBlock)
  32. {
  33. int flags = fcntl (fd, F_GETFL);
  34. fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
  35. : (flags | O_NONBLOCK)));
  36. }
  37. int getFd() const { return inChannel; }
  38. void tryNextRead()
  39. {
  40. while (true)
  41. {
  42. size_t len = (receivingLength ? sizeof (size_t) : bufferLength.len);
  43. if (! receivingLength)
  44. buffer.realloc (len);
  45. char* dst = (receivingLength ? bufferLength.data : buffer.getData());
  46. ssize_t actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
  47. if (actual < 0)
  48. {
  49. if (errno == EINTR)
  50. continue;
  51. break;
  52. }
  53. pos += static_cast<size_t> (actual);
  54. if (pos == len)
  55. {
  56. pos = 0;
  57. if (! receivingLength)
  58. parseJSON (String (buffer.getData(), bufferLength.len));
  59. receivingLength = (! receivingLength);
  60. }
  61. }
  62. if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
  63. responder->receiverHadError();
  64. }
  65. static void sendCommand (int outChannel, const String& cmd, const var& params)
  66. {
  67. DynamicObject::Ptr obj = new DynamicObject;
  68. obj->setProperty (getCmdIdentifier(), cmd);
  69. if (! params.isVoid())
  70. obj->setProperty (getParamIdentifier(), params);
  71. String json (JSON::toString (var (obj)));
  72. size_t jsonLength = static_cast<size_t> (json.length());
  73. size_t len = sizeof (size_t) + jsonLength;
  74. HeapBlock<char> buffer (len);
  75. char* dst = buffer.getData();
  76. memcpy (dst, &jsonLength, sizeof (size_t));
  77. dst += sizeof (size_t);
  78. memcpy (dst, json.toRawUTF8(), jsonLength);
  79. ssize_t ret;
  80. do
  81. {
  82. ret = write (outChannel, buffer.getData(), len);
  83. } while (ret == -1 && errno == EINTR);
  84. }
  85. private:
  86. void parseJSON (const String& json)
  87. {
  88. var object (JSON::fromString (json));
  89. if (! object.isVoid())
  90. {
  91. String cmd (object.getProperty (getCmdIdentifier(), var()).toString());
  92. var params (object.getProperty (getParamIdentifier(), var()));
  93. if (responder != nullptr)
  94. responder->handleCommand (cmd, params);
  95. }
  96. }
  97. static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; }
  98. static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; }
  99. Responder* responder;
  100. int inChannel;
  101. size_t pos = 0;
  102. bool receivingLength = true;
  103. union { char data [sizeof (size_t)]; size_t len; } bufferLength;
  104. HeapBlock<char> buffer;
  105. };
  106. //==============================================================================
  107. class GtkChildProcess : private CommandReceiver::Responder
  108. {
  109. public:
  110. //==============================================================================
  111. GtkChildProcess (int inChannel, int outChannelToUse)
  112. : outChannel (outChannelToUse), receiver (this, inChannel)
  113. {}
  114. void entry()
  115. {
  116. gtk_init (nullptr, nullptr);
  117. GtkWidget *plug;
  118. plug = gtk_plug_new(0);
  119. GtkWidget* container;
  120. container = gtk_scrolled_window_new (nullptr, nullptr);
  121. webview = webkit_web_view_new();
  122. gtk_container_add (GTK_CONTAINER (container), webview);
  123. gtk_container_add (GTK_CONTAINER (plug), container);
  124. webkit_web_view_load_uri (WEBKIT_WEB_VIEW (webview), "about:blank");
  125. g_signal_connect (WEBKIT_WEB_VIEW (webview), "navigation-policy-decision-requested",
  126. G_CALLBACK (navigationPolicyDecisionCallback), this);
  127. g_signal_connect (WEBKIT_WEB_VIEW (webview), "new-window-policy-decision-requested",
  128. G_CALLBACK (newWindowPolicyDecisionCallback), this);
  129. g_signal_connect (WEBKIT_WEB_VIEW (webview), "document-load-finished",
  130. G_CALLBACK (documentLoadFinishedCallback), this);
  131. gtk_widget_show_all (plug);
  132. unsigned long wID = (unsigned long) gtk_plug_get_id (GTK_PLUG (plug));
  133. CommandReceiver::setBlocking (outChannel, true);
  134. ssize_t ret;
  135. do {
  136. ret = write (outChannel, &wID, sizeof (wID));
  137. } while (ret == -1 && errno == EINTR);
  138. g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
  139. receiver.tryNextRead();
  140. gtk_main();
  141. }
  142. void goToURL (const var& params)
  143. {
  144. static Identifier urlIdentifier ("url");
  145. String url (params.getProperty (urlIdentifier, var()).toString());
  146. webkit_web_view_load_uri (WEBKIT_WEB_VIEW (webview), url.toRawUTF8());
  147. }
  148. void handleDecisionResponse (const var& params)
  149. {
  150. WebKitWebPolicyDecision* decision
  151. = (WebKitWebPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
  152. bool allow = params.getProperty ("allow", var (false));
  153. if (decision != nullptr && decisions.contains (decision))
  154. {
  155. if (allow)
  156. webkit_web_policy_decision_use (decision);
  157. else
  158. webkit_web_policy_decision_ignore (decision);
  159. decisions.removeAllInstancesOf (decision);
  160. g_object_unref (decision);
  161. }
  162. }
  163. //==============================================================================
  164. void handleCommand (const String& cmd, const var& params) override
  165. {
  166. if (cmd == "quit") quit();
  167. else if (cmd == "goToURL") goToURL (params);
  168. else if (cmd == "goBack") webkit_web_view_go_back (WEBKIT_WEB_VIEW (webview));
  169. else if (cmd == "goForward") webkit_web_view_go_forward (WEBKIT_WEB_VIEW (webview));
  170. else if (cmd == "refresh") webkit_web_view_reload (WEBKIT_WEB_VIEW (webview));
  171. else if (cmd == "stop") webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (webview));
  172. else if (cmd == "decision") handleDecisionResponse (params);
  173. }
  174. void receiverHadError() override
  175. {
  176. exit (-1);
  177. }
  178. //==============================================================================
  179. bool pipeReady (gint fd, GIOCondition)
  180. {
  181. if (fd == receiver.getFd())
  182. {
  183. receiver.tryNextRead();
  184. return true;
  185. }
  186. return false;
  187. }
  188. void quit()
  189. {
  190. exit (-1);
  191. }
  192. bool onNavigation (WebKitWebFrame* webFrame,
  193. WebKitNetworkRequest* /*request*/,
  194. WebKitWebNavigationAction* action,
  195. WebKitWebPolicyDecision* decision)
  196. {
  197. if (decision != nullptr && webkit_web_frame_find_frame (webFrame, "_top") == webFrame)
  198. {
  199. g_object_ref (decision);
  200. decisions.add (decision);
  201. DynamicObject::Ptr params = new DynamicObject;
  202. params->setProperty ("url", String (webkit_web_navigation_action_get_original_uri (action)));
  203. params->setProperty ("decision_id", (int64) decision);
  204. CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params));
  205. return true;
  206. }
  207. return false;
  208. }
  209. bool onNewWindow (WebKitWebFrame* /*webFrame*/,
  210. WebKitNetworkRequest* /*request*/,
  211. WebKitWebNavigationAction* action,
  212. WebKitWebPolicyDecision* decision)
  213. {
  214. if (decision != nullptr)
  215. {
  216. DynamicObject::Ptr params = new DynamicObject;
  217. params->setProperty ("url", String (webkit_web_navigation_action_get_original_uri (action)));
  218. CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params));
  219. // never allow new windows
  220. webkit_web_policy_decision_ignore (decision);
  221. return true;
  222. }
  223. return false;
  224. }
  225. void onLoadFinished (WebKitWebFrame* webFrame)
  226. {
  227. if (webkit_web_frame_find_frame (webFrame, "_top") == webFrame)
  228. {
  229. DynamicObject::Ptr params = new DynamicObject;
  230. params->setProperty ("url", String (webkit_web_frame_get_uri (webFrame)));
  231. CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params));
  232. }
  233. }
  234. private:
  235. static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
  236. {
  237. return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
  238. }
  239. static gboolean navigationPolicyDecisionCallback (WebKitWebView*,
  240. WebKitWebFrame* webFrame,
  241. WebKitNetworkRequest* request,
  242. WebKitWebNavigationAction* action,
  243. WebKitWebPolicyDecision* decision,
  244. gpointer user)
  245. {
  246. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  247. return (owner.onNavigation (webFrame, request, action, decision) ? TRUE : FALSE);
  248. }
  249. static gboolean newWindowPolicyDecisionCallback (WebKitWebView*,
  250. WebKitWebFrame* webFrame,
  251. WebKitNetworkRequest* request,
  252. WebKitWebNavigationAction* action,
  253. WebKitWebPolicyDecision* decision,
  254. gpointer user)
  255. {
  256. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  257. return (owner.onNewWindow (webFrame, request, action, decision) ? TRUE : FALSE);
  258. }
  259. static void documentLoadFinishedCallback (WebKitWebView*,
  260. WebKitWebFrame* webFrame,
  261. gpointer user)
  262. {
  263. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  264. owner.onLoadFinished (webFrame);
  265. }
  266. int outChannel;
  267. CommandReceiver receiver;
  268. GtkWidget* webview = nullptr;
  269. Array<WebKitWebPolicyDecision*> decisions;
  270. };
  271. //==============================================================================
  272. class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder
  273. {
  274. public:
  275. Pimpl (WebBrowserComponent& parent)
  276. : Thread ("Webview"), owner (parent)
  277. {}
  278. ~Pimpl()
  279. {
  280. quit();
  281. }
  282. //==============================================================================
  283. void init()
  284. {
  285. launchChild();
  286. int ret = pipe (threadControl);
  287. ignoreUnused (ret);
  288. jassert (ret == 0);
  289. CommandReceiver::setBlocking (inChannel, true);
  290. CommandReceiver::setBlocking (outChannel, true);
  291. CommandReceiver::setBlocking (threadControl[0], false);
  292. CommandReceiver::setBlocking (threadControl[1], true);
  293. unsigned long windowHandle;
  294. ssize_t actual = read (inChannel, &windowHandle, sizeof (windowHandle));
  295. if (actual != sizeof (windowHandle))
  296. {
  297. killChild();
  298. return;
  299. }
  300. receiver = new CommandReceiver (this, inChannel);
  301. startThread();
  302. xembed = new XEmbedComponent (windowHandle);
  303. owner.addAndMakeVisible (xembed);
  304. }
  305. void quit()
  306. {
  307. if (isThreadRunning())
  308. {
  309. signalThreadShouldExit();
  310. char ignore = 0;
  311. ssize_t ret;
  312. do
  313. {
  314. ret = write (threadControl[1], &ignore, 1);
  315. } while (ret == -1 && errno == EINTR);
  316. waitForThreadToExit (-1);
  317. receiver = nullptr;
  318. }
  319. if (childProcess != 0)
  320. {
  321. CommandReceiver::sendCommand (outChannel, "quit", var());
  322. killChild();
  323. }
  324. }
  325. //==============================================================================
  326. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
  327. {
  328. DynamicObject::Ptr params = new DynamicObject;
  329. params->setProperty ("url", url);
  330. if (headers != nullptr)
  331. params->setProperty ("headers", var (*headers));
  332. if (postData != nullptr)
  333. params->setProperty ("postData", var (*postData));
  334. CommandReceiver::sendCommand (outChannel, "goToURL", var (params));
  335. }
  336. void goBack() { CommandReceiver::sendCommand (outChannel, "goBack", var()); }
  337. void goForward() { CommandReceiver::sendCommand (outChannel, "goForward", var()); }
  338. void refresh() { CommandReceiver::sendCommand (outChannel, "refresh", var()); }
  339. void stop() { CommandReceiver::sendCommand (outChannel, "stop", var()); }
  340. void resized()
  341. {
  342. if (xembed != nullptr)
  343. xembed->setBounds (owner.getLocalBounds());
  344. }
  345. private:
  346. //==============================================================================
  347. void killChild()
  348. {
  349. if (childProcess != 0)
  350. {
  351. xembed = nullptr;
  352. kill (childProcess, SIGTERM);
  353. int status = 0;
  354. while (! WIFEXITED(status))
  355. waitpid (childProcess, &status, 0);
  356. childProcess = 0;
  357. }
  358. }
  359. void launchChild()
  360. {
  361. int ret;
  362. int inPipe[2], outPipe[2];
  363. ret = pipe (inPipe);
  364. ignoreUnused (ret); jassert (ret == 0);
  365. ret = pipe (outPipe);
  366. ignoreUnused (ret); jassert (ret == 0);
  367. int pid = fork();
  368. if (pid == 0)
  369. {
  370. close (inPipe[0]);
  371. close (outPipe[1]);
  372. GtkChildProcess child (outPipe[0], inPipe[1]);
  373. child.entry();
  374. exit (0);
  375. }
  376. close (inPipe[1]);
  377. close (outPipe[0]);
  378. inChannel = inPipe[0];
  379. outChannel = outPipe[1];
  380. childProcess = pid;
  381. }
  382. void run() override
  383. {
  384. while (! threadShouldExit())
  385. {
  386. if (shouldExit())
  387. return;
  388. receiver->tryNextRead();
  389. fd_set set;
  390. FD_ZERO (&set);
  391. FD_SET (threadControl[0], &set);
  392. FD_SET (receiver->getFd(), &set);
  393. int max_fd = jmax (threadControl[0], receiver->getFd());
  394. int result = 0;
  395. while (result == 0 || (result < 0 && errno == EINTR))
  396. result = select (max_fd + 1, &set, NULL, NULL, NULL);
  397. if (result < 0)
  398. break;
  399. }
  400. }
  401. bool shouldExit()
  402. {
  403. char ignore;
  404. ssize_t result = read (threadControl[0], &ignore, 1);
  405. return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
  406. }
  407. //==============================================================================
  408. void handleCommandOnMessageThread (const String& cmd, const var& params)
  409. {
  410. String url (params.getProperty ("url", var()).toString());
  411. if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
  412. else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
  413. else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
  414. else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
  415. threadBlocker.signal();
  416. }
  417. void handlePageAboutToLoad (const String& url, const var& inputParams)
  418. {
  419. int64 decision_id = inputParams.getProperty ("decision_id", var (0));
  420. if (decision_id != 0)
  421. {
  422. DynamicObject::Ptr params = new DynamicObject;
  423. params->setProperty ("decision_id", decision_id);
  424. params->setProperty ("allow", owner.pageAboutToLoad (url));
  425. CommandReceiver::sendCommand (outChannel, "decision", var (params));
  426. }
  427. }
  428. void handleCommand (const String& cmd, const var& params) override
  429. {
  430. threadBlocker.reset();
  431. (new HandleOnMessageThread (this, cmd, params))->post();
  432. // wait until the command has executed on the message thread
  433. // this ensures that Pimpl can never be deleted while the
  434. // message has not been executed yet
  435. threadBlocker.wait (-1);
  436. }
  437. void receiverHadError() override {}
  438. //==============================================================================
  439. struct HandleOnMessageThread : public CallbackMessage
  440. {
  441. HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
  442. : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
  443. {}
  444. void messageCallback() override
  445. {
  446. owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
  447. }
  448. Pimpl* owner;
  449. String cmdToSend;
  450. var paramsToSend;
  451. };
  452. private:
  453. WebBrowserComponent& owner;
  454. ScopedPointer<CommandReceiver> receiver;
  455. int childProcess = 0, inChannel = 0, outChannel = 0;
  456. int threadControl[2];
  457. ScopedPointer<XEmbedComponent> xembed;
  458. WaitableEvent threadBlocker;
  459. };
  460. //==============================================================================
  461. WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
  462. : browser (new Pimpl (*this)),
  463. blankPageShown (false),
  464. unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
  465. {
  466. setOpaque (true);
  467. browser->init();
  468. }
  469. WebBrowserComponent::~WebBrowserComponent()
  470. {
  471. if (browser != nullptr)
  472. {
  473. delete browser;
  474. browser = nullptr;
  475. }
  476. }
  477. //==============================================================================
  478. void WebBrowserComponent::goToURL (const String& url,
  479. const StringArray* headers,
  480. const MemoryBlock* postData)
  481. {
  482. lastURL = url;
  483. if (headers != nullptr)
  484. lastHeaders = *headers;
  485. else
  486. lastHeaders.clear();
  487. if (postData != nullptr)
  488. lastPostData = *postData;
  489. else
  490. lastPostData.reset();
  491. blankPageShown = false;
  492. browser->goToURL (url, headers, postData);
  493. }
  494. void WebBrowserComponent::stop()
  495. {
  496. browser->stop();
  497. }
  498. void WebBrowserComponent::goBack()
  499. {
  500. lastURL.clear();
  501. blankPageShown = false;
  502. browser->goBack();
  503. }
  504. void WebBrowserComponent::goForward()
  505. {
  506. lastURL.clear();
  507. browser->goForward();
  508. }
  509. void WebBrowserComponent::refresh()
  510. {
  511. browser->refresh();
  512. }
  513. //==============================================================================
  514. void WebBrowserComponent::paint (Graphics& g)
  515. {
  516. g.fillAll (Colours::white);
  517. }
  518. void WebBrowserComponent::checkWindowAssociation()
  519. {
  520. }
  521. void WebBrowserComponent::reloadLastURL()
  522. {
  523. if (lastURL.isNotEmpty())
  524. {
  525. goToURL (lastURL, &lastHeaders, &lastPostData);
  526. lastURL.clear();
  527. }
  528. }
  529. void WebBrowserComponent::parentHierarchyChanged()
  530. {
  531. checkWindowAssociation();
  532. }
  533. void WebBrowserComponent::resized()
  534. {
  535. if (browser != nullptr)
  536. browser->resized();
  537. }
  538. void WebBrowserComponent::visibilityChanged()
  539. {
  540. checkWindowAssociation();
  541. }
  542. void WebBrowserComponent::focusGained (FocusChangeType)
  543. {
  544. }