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.

769 lines
23KB

  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. CommandReceiver::setBlocking (outChannel, true);
  117. gtk_init (nullptr, nullptr);
  118. WebKitSettings* settings = webkit_settings_new();
  119. webkit_settings_set_hardware_acceleration_policy (settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
  120. GtkWidget *plug;
  121. plug = gtk_plug_new(0);
  122. GtkWidget* container;
  123. container = gtk_scrolled_window_new (nullptr, nullptr);
  124. GtkWidget* webviewWidget = webkit_web_view_new_with_settings (settings);
  125. webview = WEBKIT_WEB_VIEW (webviewWidget);
  126. gtk_container_add (GTK_CONTAINER (container), webviewWidget);
  127. gtk_container_add (GTK_CONTAINER (plug), container);
  128. webkit_web_view_load_uri (webview, "about:blank");
  129. g_signal_connect (webview, "decide-policy",
  130. G_CALLBACK (decidePolicyCallback), this);
  131. g_signal_connect (webview, "load-changed",
  132. G_CALLBACK (loadChangedCallback), this);
  133. g_signal_connect (webview, "load-failed",
  134. G_CALLBACK (loadFailedCallback), this);
  135. gtk_widget_show_all (plug);
  136. unsigned long wID = (unsigned long) gtk_plug_get_id (GTK_PLUG (plug));
  137. ssize_t ret;
  138. do {
  139. ret = write (outChannel, &wID, sizeof (wID));
  140. } while (ret == -1 && errno == EINTR);
  141. g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
  142. receiver.tryNextRead();
  143. gtk_main();
  144. }
  145. void goToURL (const var& params)
  146. {
  147. static Identifier urlIdentifier ("url");
  148. String url (params.getProperty (urlIdentifier, var()).toString());
  149. webkit_web_view_load_uri (webview, url.toRawUTF8());
  150. }
  151. void handleDecisionResponse (const var& params)
  152. {
  153. WebKitPolicyDecision* decision
  154. = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
  155. bool allow = params.getProperty ("allow", var (false));
  156. if (decision != nullptr && decisions.contains (decision))
  157. {
  158. if (allow)
  159. webkit_policy_decision_use (decision);
  160. else
  161. webkit_policy_decision_ignore (decision);
  162. decisions.removeAllInstancesOf (decision);
  163. g_object_unref (decision);
  164. }
  165. }
  166. //==============================================================================
  167. void handleCommand (const String& cmd, const var& params) override
  168. {
  169. if (cmd == "quit") quit();
  170. else if (cmd == "goToURL") goToURL (params);
  171. else if (cmd == "goBack") webkit_web_view_go_back (webview);
  172. else if (cmd == "goForward") webkit_web_view_go_forward (webview);
  173. else if (cmd == "refresh") webkit_web_view_reload (webview);
  174. else if (cmd == "stop") webkit_web_view_stop_loading (webview);
  175. else if (cmd == "decision") handleDecisionResponse (params);
  176. }
  177. void receiverHadError() override
  178. {
  179. exit (-1);
  180. }
  181. //==============================================================================
  182. bool pipeReady (gint fd, GIOCondition)
  183. {
  184. if (fd == receiver.getFd())
  185. {
  186. receiver.tryNextRead();
  187. return true;
  188. }
  189. return false;
  190. }
  191. void quit()
  192. {
  193. exit (-1);
  194. }
  195. bool onNavigation (String frameName,
  196. WebKitNavigationAction* action,
  197. WebKitPolicyDecision* decision)
  198. {
  199. if (decision != nullptr && frameName.isEmpty())
  200. {
  201. g_object_ref (decision);
  202. decisions.add (decision);
  203. DynamicObject::Ptr params = new DynamicObject;
  204. params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
  205. params->setProperty ("decision_id", (int64) decision);
  206. CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params));
  207. return true;
  208. }
  209. return false;
  210. }
  211. bool onNewWindow (String /*frameName*/,
  212. WebKitNavigationAction* action,
  213. WebKitPolicyDecision* decision)
  214. {
  215. if (decision != nullptr)
  216. {
  217. DynamicObject::Ptr params = new DynamicObject;
  218. params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
  219. CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params));
  220. // never allow new windows
  221. webkit_policy_decision_ignore (decision);
  222. return true;
  223. }
  224. return false;
  225. }
  226. void onLoadChanged (WebKitLoadEvent loadEvent)
  227. {
  228. if (loadEvent == WEBKIT_LOAD_FINISHED)
  229. {
  230. DynamicObject::Ptr params = new DynamicObject;
  231. params->setProperty ("url", String (webkit_web_view_get_uri (webview)));
  232. CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params));
  233. }
  234. }
  235. bool onDecidePolicy (WebKitPolicyDecision* decision,
  236. WebKitPolicyDecisionType decisionType)
  237. {
  238. switch (decisionType)
  239. {
  240. case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
  241. {
  242. WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  243. const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  244. return onNavigation (String (frameName != nullptr ? frameName : ""),
  245. webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  246. decision);
  247. }
  248. break;
  249. case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
  250. {
  251. WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  252. const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  253. return onNewWindow (String (frameName != nullptr ? frameName : ""),
  254. webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  255. decision);
  256. }
  257. break;
  258. case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
  259. {
  260. WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);
  261. // for now just always allow response requests
  262. ignoreUnused (response);
  263. webkit_policy_decision_use (decision);
  264. return true;
  265. }
  266. break;
  267. default:
  268. break;
  269. }
  270. return false;
  271. }
  272. void onLoadFailed (GError* error)
  273. {
  274. DynamicObject::Ptr params = new DynamicObject;
  275. params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
  276. CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params));
  277. }
  278. private:
  279. static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
  280. {
  281. return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
  282. }
  283. static gboolean decidePolicyCallback (WebKitWebView*,
  284. WebKitPolicyDecision* decision,
  285. WebKitPolicyDecisionType decisionType,
  286. gpointer user)
  287. {
  288. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  289. return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
  290. }
  291. static void loadChangedCallback (WebKitWebView*,
  292. WebKitLoadEvent loadEvent,
  293. gpointer user)
  294. {
  295. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  296. owner.onLoadChanged (loadEvent);
  297. }
  298. static void loadFailedCallback (WebKitWebView*,
  299. WebKitLoadEvent /*loadEvent*/,
  300. gchar* /*failing_uri*/,
  301. GError* error,
  302. gpointer user)
  303. {
  304. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  305. owner.onLoadFailed (error);
  306. }
  307. int outChannel;
  308. CommandReceiver receiver;
  309. WebKitWebView* webview = nullptr;
  310. Array<WebKitPolicyDecision*> decisions;
  311. };
  312. //==============================================================================
  313. class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder
  314. {
  315. public:
  316. Pimpl (WebBrowserComponent& parent)
  317. : Thread ("Webview"), owner (parent)
  318. {}
  319. ~Pimpl()
  320. {
  321. quit();
  322. }
  323. //==============================================================================
  324. void init()
  325. {
  326. launchChild();
  327. int ret = pipe (threadControl);
  328. ignoreUnused (ret);
  329. jassert (ret == 0);
  330. CommandReceiver::setBlocking (inChannel, true);
  331. CommandReceiver::setBlocking (outChannel, true);
  332. CommandReceiver::setBlocking (threadControl[0], false);
  333. CommandReceiver::setBlocking (threadControl[1], true);
  334. unsigned long windowHandle;
  335. ssize_t actual = read (inChannel, &windowHandle, sizeof (windowHandle));
  336. if (actual != sizeof (windowHandle))
  337. {
  338. killChild();
  339. return;
  340. }
  341. receiver = new CommandReceiver (this, inChannel);
  342. startThread();
  343. xembed = new XEmbedComponent (windowHandle);
  344. owner.addAndMakeVisible (xembed);
  345. }
  346. void quit()
  347. {
  348. if (isThreadRunning())
  349. {
  350. signalThreadShouldExit();
  351. char ignore = 0;
  352. ssize_t ret;
  353. do
  354. {
  355. ret = write (threadControl[1], &ignore, 1);
  356. } while (ret == -1 && errno == EINTR);
  357. waitForThreadToExit (-1);
  358. receiver = nullptr;
  359. }
  360. if (childProcess != 0)
  361. {
  362. CommandReceiver::sendCommand (outChannel, "quit", var());
  363. killChild();
  364. }
  365. }
  366. //==============================================================================
  367. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
  368. {
  369. DynamicObject::Ptr params = new DynamicObject;
  370. params->setProperty ("url", url);
  371. if (headers != nullptr)
  372. params->setProperty ("headers", var (*headers));
  373. if (postData != nullptr)
  374. params->setProperty ("postData", var (*postData));
  375. CommandReceiver::sendCommand (outChannel, "goToURL", var (params));
  376. }
  377. void goBack() { CommandReceiver::sendCommand (outChannel, "goBack", var()); }
  378. void goForward() { CommandReceiver::sendCommand (outChannel, "goForward", var()); }
  379. void refresh() { CommandReceiver::sendCommand (outChannel, "refresh", var()); }
  380. void stop() { CommandReceiver::sendCommand (outChannel, "stop", var()); }
  381. void resized()
  382. {
  383. if (xembed != nullptr)
  384. xembed->setBounds (owner.getLocalBounds());
  385. }
  386. private:
  387. //==============================================================================
  388. void killChild()
  389. {
  390. if (childProcess != 0)
  391. {
  392. xembed = nullptr;
  393. kill (childProcess, SIGTERM);
  394. int status = 0;
  395. while (! WIFEXITED(status))
  396. waitpid (childProcess, &status, 0);
  397. childProcess = 0;
  398. }
  399. }
  400. void launchChild()
  401. {
  402. int ret;
  403. int inPipe[2], outPipe[2];
  404. ret = pipe (inPipe);
  405. ignoreUnused (ret); jassert (ret == 0);
  406. ret = pipe (outPipe);
  407. ignoreUnused (ret); jassert (ret == 0);
  408. int pid = fork();
  409. if (pid == 0)
  410. {
  411. close (inPipe[0]);
  412. close (outPipe[1]);
  413. GtkChildProcess child (outPipe[0], inPipe[1]);
  414. child.entry();
  415. exit (0);
  416. }
  417. close (inPipe[1]);
  418. close (outPipe[0]);
  419. inChannel = inPipe[0];
  420. outChannel = outPipe[1];
  421. childProcess = pid;
  422. }
  423. void run() override
  424. {
  425. while (! threadShouldExit())
  426. {
  427. if (shouldExit())
  428. return;
  429. receiver->tryNextRead();
  430. fd_set set;
  431. FD_ZERO (&set);
  432. FD_SET (threadControl[0], &set);
  433. FD_SET (receiver->getFd(), &set);
  434. int max_fd = jmax (threadControl[0], receiver->getFd());
  435. int result = 0;
  436. while (result == 0 || (result < 0 && errno == EINTR))
  437. result = select (max_fd + 1, &set, NULL, NULL, NULL);
  438. if (result < 0)
  439. break;
  440. }
  441. }
  442. bool shouldExit()
  443. {
  444. char ignore;
  445. ssize_t result = read (threadControl[0], &ignore, 1);
  446. return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
  447. }
  448. //==============================================================================
  449. void handleCommandOnMessageThread (const String& cmd, const var& params)
  450. {
  451. String url (params.getProperty ("url", var()).toString());
  452. if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
  453. else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
  454. else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
  455. else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
  456. else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
  457. threadBlocker.signal();
  458. }
  459. void handlePageAboutToLoad (const String& url, const var& inputParams)
  460. {
  461. int64 decision_id = inputParams.getProperty ("decision_id", var (0));
  462. if (decision_id != 0)
  463. {
  464. DynamicObject::Ptr params = new DynamicObject;
  465. params->setProperty ("decision_id", decision_id);
  466. params->setProperty ("allow", owner.pageAboutToLoad (url));
  467. CommandReceiver::sendCommand (outChannel, "decision", var (params));
  468. }
  469. }
  470. void handlePageLoadHadNetworkError (const var& params)
  471. {
  472. String error = params.getProperty ("error", "Unknown error");
  473. if (owner.pageLoadHadNetworkError (error))
  474. goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
  475. }
  476. void handleCommand (const String& cmd, const var& params) override
  477. {
  478. threadBlocker.reset();
  479. (new HandleOnMessageThread (this, cmd, params))->post();
  480. // wait until the command has executed on the message thread
  481. // this ensures that Pimpl can never be deleted while the
  482. // message has not been executed yet
  483. threadBlocker.wait (-1);
  484. }
  485. void receiverHadError() override {}
  486. //==============================================================================
  487. struct HandleOnMessageThread : public CallbackMessage
  488. {
  489. HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
  490. : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
  491. {}
  492. void messageCallback() override
  493. {
  494. owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
  495. }
  496. Pimpl* owner;
  497. String cmdToSend;
  498. var paramsToSend;
  499. };
  500. private:
  501. WebBrowserComponent& owner;
  502. ScopedPointer<CommandReceiver> receiver;
  503. int childProcess = 0, inChannel = 0, outChannel = 0;
  504. int threadControl[2];
  505. ScopedPointer<XEmbedComponent> xembed;
  506. WaitableEvent threadBlocker;
  507. };
  508. //==============================================================================
  509. WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
  510. : browser (new Pimpl (*this)),
  511. blankPageShown (false),
  512. unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
  513. {
  514. setOpaque (true);
  515. browser->init();
  516. }
  517. WebBrowserComponent::~WebBrowserComponent()
  518. {
  519. if (browser != nullptr)
  520. {
  521. delete browser;
  522. browser = nullptr;
  523. }
  524. }
  525. //==============================================================================
  526. void WebBrowserComponent::goToURL (const String& url,
  527. const StringArray* headers,
  528. const MemoryBlock* postData)
  529. {
  530. lastURL = url;
  531. if (headers != nullptr)
  532. lastHeaders = *headers;
  533. else
  534. lastHeaders.clear();
  535. if (postData != nullptr)
  536. lastPostData = *postData;
  537. else
  538. lastPostData.reset();
  539. blankPageShown = false;
  540. browser->goToURL (url, headers, postData);
  541. }
  542. void WebBrowserComponent::stop()
  543. {
  544. browser->stop();
  545. }
  546. void WebBrowserComponent::goBack()
  547. {
  548. lastURL.clear();
  549. blankPageShown = false;
  550. browser->goBack();
  551. }
  552. void WebBrowserComponent::goForward()
  553. {
  554. lastURL.clear();
  555. browser->goForward();
  556. }
  557. void WebBrowserComponent::refresh()
  558. {
  559. browser->refresh();
  560. }
  561. //==============================================================================
  562. void WebBrowserComponent::paint (Graphics& g)
  563. {
  564. g.fillAll (Colours::white);
  565. }
  566. void WebBrowserComponent::checkWindowAssociation()
  567. {
  568. }
  569. void WebBrowserComponent::reloadLastURL()
  570. {
  571. if (lastURL.isNotEmpty())
  572. {
  573. goToURL (lastURL, &lastHeaders, &lastPostData);
  574. lastURL.clear();
  575. }
  576. }
  577. void WebBrowserComponent::parentHierarchyChanged()
  578. {
  579. checkWindowAssociation();
  580. }
  581. void WebBrowserComponent::resized()
  582. {
  583. if (browser != nullptr)
  584. browser->resized();
  585. }
  586. void WebBrowserComponent::visibilityChanged()
  587. {
  588. checkWindowAssociation();
  589. }
  590. void WebBrowserComponent::focusGained (FocusChangeType)
  591. {
  592. }
  593. void WebBrowserComponent::clearCookies()
  594. {
  595. // Currently not implemented on linux as WebBrowserComponent currently does not
  596. // store cookies on linux
  597. jassertfalse;
  598. }