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.

815 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. //==============================================================================
  20. extern int juce_gtkWebkitMain (int argc, const char* argv[]);
  21. class CommandReceiver
  22. {
  23. public:
  24. struct Responder
  25. {
  26. virtual ~Responder() {}
  27. virtual void handleCommand (const String& cmd, const var& param) = 0;
  28. virtual void receiverHadError() = 0;
  29. };
  30. CommandReceiver (Responder* responderToUse, int inputChannelToUse)
  31. : responder (responderToUse), inChannel (inputChannelToUse)
  32. {
  33. setBlocking (inChannel, false);
  34. }
  35. static void setBlocking (int fd, bool shouldBlock)
  36. {
  37. int flags = fcntl (fd, F_GETFL);
  38. fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
  39. : (flags | O_NONBLOCK)));
  40. }
  41. int getFd() const { return inChannel; }
  42. void tryNextRead()
  43. {
  44. while (true)
  45. {
  46. size_t len = (receivingLength ? sizeof (size_t) : bufferLength.len);
  47. if (! receivingLength)
  48. buffer.realloc (len);
  49. char* dst = (receivingLength ? bufferLength.data : buffer.getData());
  50. ssize_t actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
  51. if (actual < 0)
  52. {
  53. if (errno == EINTR)
  54. continue;
  55. break;
  56. }
  57. pos += static_cast<size_t> (actual);
  58. if (pos == len)
  59. {
  60. pos = 0;
  61. if (! receivingLength)
  62. parseJSON (String (buffer.getData(), bufferLength.len));
  63. receivingLength = (! receivingLength);
  64. }
  65. }
  66. if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
  67. responder->receiverHadError();
  68. }
  69. static void sendCommand (int outChannel, const String& cmd, const var& params)
  70. {
  71. DynamicObject::Ptr obj = new DynamicObject;
  72. obj->setProperty (getCmdIdentifier(), cmd);
  73. if (! params.isVoid())
  74. obj->setProperty (getParamIdentifier(), params);
  75. String json (JSON::toString (var (obj)));
  76. size_t jsonLength = static_cast<size_t> (json.length());
  77. size_t len = sizeof (size_t) + jsonLength;
  78. HeapBlock<char> buffer (len);
  79. char* dst = buffer.getData();
  80. memcpy (dst, &jsonLength, sizeof (size_t));
  81. dst += sizeof (size_t);
  82. memcpy (dst, json.toRawUTF8(), jsonLength);
  83. ssize_t ret;
  84. do
  85. {
  86. ret = write (outChannel, buffer.getData(), len);
  87. } while (ret == -1 && errno == EINTR);
  88. }
  89. private:
  90. void parseJSON (const String& json)
  91. {
  92. var object (JSON::fromString (json));
  93. if (! object.isVoid())
  94. {
  95. String cmd (object.getProperty (getCmdIdentifier(), var()).toString());
  96. var params (object.getProperty (getParamIdentifier(), var()));
  97. if (responder != nullptr)
  98. responder->handleCommand (cmd, params);
  99. }
  100. }
  101. static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; }
  102. static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; }
  103. Responder* responder;
  104. int inChannel;
  105. size_t pos = 0;
  106. bool receivingLength = true;
  107. union { char data [sizeof (size_t)]; size_t len; } bufferLength;
  108. HeapBlock<char> buffer;
  109. };
  110. //==============================================================================
  111. class GtkChildProcess : private CommandReceiver::Responder
  112. {
  113. public:
  114. //==============================================================================
  115. GtkChildProcess (int inChannel, int outChannelToUse)
  116. : outChannel (outChannelToUse), receiver (this, inChannel)
  117. {}
  118. int entry()
  119. {
  120. CommandReceiver::setBlocking (outChannel, true);
  121. gtk_init (nullptr, nullptr);
  122. WebKitSettings* settings = webkit_settings_new();
  123. webkit_settings_set_hardware_acceleration_policy (settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
  124. GtkWidget *plug;
  125. plug = gtk_plug_new(0);
  126. GtkWidget* container;
  127. container = gtk_scrolled_window_new (nullptr, nullptr);
  128. GtkWidget* webviewWidget = webkit_web_view_new_with_settings (settings);
  129. webview = WEBKIT_WEB_VIEW (webviewWidget);
  130. gtk_container_add (GTK_CONTAINER (container), webviewWidget);
  131. gtk_container_add (GTK_CONTAINER (plug), container);
  132. webkit_web_view_load_uri (webview, "about:blank");
  133. g_signal_connect (webview, "decide-policy",
  134. G_CALLBACK (decidePolicyCallback), this);
  135. g_signal_connect (webview, "load-changed",
  136. G_CALLBACK (loadChangedCallback), this);
  137. g_signal_connect (webview, "load-failed",
  138. G_CALLBACK (loadFailedCallback), this);
  139. gtk_widget_show_all (plug);
  140. unsigned long wID = (unsigned long) gtk_plug_get_id (GTK_PLUG (plug));
  141. ssize_t ret;
  142. do {
  143. ret = write (outChannel, &wID, sizeof (wID));
  144. } while (ret == -1 && errno == EINTR);
  145. g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
  146. receiver.tryNextRead();
  147. gtk_main();
  148. return 0;
  149. }
  150. void goToURL (const var& params)
  151. {
  152. static Identifier urlIdentifier ("url");
  153. String url (params.getProperty (urlIdentifier, var()).toString());
  154. webkit_web_view_load_uri (webview, url.toRawUTF8());
  155. }
  156. void handleDecisionResponse (const var& params)
  157. {
  158. WebKitPolicyDecision* decision
  159. = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
  160. bool allow = params.getProperty ("allow", var (false));
  161. if (decision != nullptr && decisions.contains (decision))
  162. {
  163. if (allow)
  164. webkit_policy_decision_use (decision);
  165. else
  166. webkit_policy_decision_ignore (decision);
  167. decisions.removeAllInstancesOf (decision);
  168. g_object_unref (decision);
  169. }
  170. }
  171. //==============================================================================
  172. void handleCommand (const String& cmd, const var& params) override
  173. {
  174. if (cmd == "quit") quit();
  175. else if (cmd == "goToURL") goToURL (params);
  176. else if (cmd == "goBack") webkit_web_view_go_back (webview);
  177. else if (cmd == "goForward") webkit_web_view_go_forward (webview);
  178. else if (cmd == "refresh") webkit_web_view_reload (webview);
  179. else if (cmd == "stop") webkit_web_view_stop_loading (webview);
  180. else if (cmd == "decision") handleDecisionResponse (params);
  181. }
  182. void receiverHadError() override
  183. {
  184. exit (-1);
  185. }
  186. //==============================================================================
  187. bool pipeReady (gint fd, GIOCondition)
  188. {
  189. if (fd == receiver.getFd())
  190. {
  191. receiver.tryNextRead();
  192. return true;
  193. }
  194. return false;
  195. }
  196. void quit()
  197. {
  198. gtk_main_quit();
  199. }
  200. bool onNavigation (String frameName,
  201. WebKitNavigationAction* action,
  202. WebKitPolicyDecision* decision)
  203. {
  204. if (decision != nullptr && frameName.isEmpty())
  205. {
  206. g_object_ref (decision);
  207. decisions.add (decision);
  208. DynamicObject::Ptr params = new DynamicObject;
  209. params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
  210. params->setProperty ("decision_id", (int64) decision);
  211. CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params));
  212. return true;
  213. }
  214. return false;
  215. }
  216. bool onNewWindow (String /*frameName*/,
  217. WebKitNavigationAction* action,
  218. WebKitPolicyDecision* decision)
  219. {
  220. if (decision != nullptr)
  221. {
  222. DynamicObject::Ptr params = new DynamicObject;
  223. params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
  224. CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params));
  225. // never allow new windows
  226. webkit_policy_decision_ignore (decision);
  227. return true;
  228. }
  229. return false;
  230. }
  231. void onLoadChanged (WebKitLoadEvent loadEvent)
  232. {
  233. if (loadEvent == WEBKIT_LOAD_FINISHED)
  234. {
  235. DynamicObject::Ptr params = new DynamicObject;
  236. params->setProperty ("url", String (webkit_web_view_get_uri (webview)));
  237. CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params));
  238. }
  239. }
  240. bool onDecidePolicy (WebKitPolicyDecision* decision,
  241. WebKitPolicyDecisionType decisionType)
  242. {
  243. switch (decisionType)
  244. {
  245. case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
  246. {
  247. WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  248. const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  249. return onNavigation (String (frameName != nullptr ? frameName : ""),
  250. webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  251. decision);
  252. }
  253. break;
  254. case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
  255. {
  256. WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  257. const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  258. return onNewWindow (String (frameName != nullptr ? frameName : ""),
  259. webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  260. decision);
  261. }
  262. break;
  263. case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
  264. {
  265. WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);
  266. // for now just always allow response requests
  267. ignoreUnused (response);
  268. webkit_policy_decision_use (decision);
  269. return true;
  270. }
  271. break;
  272. default:
  273. break;
  274. }
  275. return false;
  276. }
  277. void onLoadFailed (GError* error)
  278. {
  279. DynamicObject::Ptr params = new DynamicObject;
  280. params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
  281. CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params));
  282. }
  283. private:
  284. static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
  285. {
  286. return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
  287. }
  288. static gboolean decidePolicyCallback (WebKitWebView*,
  289. WebKitPolicyDecision* decision,
  290. WebKitPolicyDecisionType decisionType,
  291. gpointer user)
  292. {
  293. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  294. return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
  295. }
  296. static void loadChangedCallback (WebKitWebView*,
  297. WebKitLoadEvent loadEvent,
  298. gpointer user)
  299. {
  300. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  301. owner.onLoadChanged (loadEvent);
  302. }
  303. static void loadFailedCallback (WebKitWebView*,
  304. WebKitLoadEvent /*loadEvent*/,
  305. gchar* /*failing_uri*/,
  306. GError* error,
  307. gpointer user)
  308. {
  309. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  310. owner.onLoadFailed (error);
  311. }
  312. int outChannel;
  313. CommandReceiver receiver;
  314. WebKitWebView* webview = nullptr;
  315. Array<WebKitPolicyDecision*> decisions;
  316. };
  317. //==============================================================================
  318. class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder
  319. {
  320. public:
  321. Pimpl (WebBrowserComponent& parent)
  322. : Thread ("Webview"), owner (parent)
  323. {}
  324. ~Pimpl()
  325. {
  326. quit();
  327. }
  328. //==============================================================================
  329. void init()
  330. {
  331. launchChild();
  332. int ret = pipe (threadControl);
  333. ignoreUnused (ret);
  334. jassert (ret == 0);
  335. CommandReceiver::setBlocking (inChannel, true);
  336. CommandReceiver::setBlocking (outChannel, true);
  337. CommandReceiver::setBlocking (threadControl[0], false);
  338. CommandReceiver::setBlocking (threadControl[1], true);
  339. unsigned long windowHandle;
  340. ssize_t actual = read (inChannel, &windowHandle, sizeof (windowHandle));
  341. if (actual != sizeof (windowHandle))
  342. {
  343. killChild();
  344. return;
  345. }
  346. receiver = new CommandReceiver (this, inChannel);
  347. startThread();
  348. xembed = new XEmbedComponent (windowHandle);
  349. owner.addAndMakeVisible (xembed);
  350. }
  351. void quit()
  352. {
  353. if (isThreadRunning())
  354. {
  355. signalThreadShouldExit();
  356. char ignore = 0;
  357. ssize_t ret;
  358. do
  359. {
  360. ret = write (threadControl[1], &ignore, 1);
  361. } while (ret == -1 && errno == EINTR);
  362. waitForThreadToExit (-1);
  363. receiver = nullptr;
  364. }
  365. if (childProcess != 0)
  366. {
  367. CommandReceiver::sendCommand (outChannel, "quit", var());
  368. killChild();
  369. }
  370. }
  371. //==============================================================================
  372. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
  373. {
  374. DynamicObject::Ptr params = new DynamicObject;
  375. params->setProperty ("url", url);
  376. if (headers != nullptr)
  377. params->setProperty ("headers", var (*headers));
  378. if (postData != nullptr)
  379. params->setProperty ("postData", var (*postData));
  380. CommandReceiver::sendCommand (outChannel, "goToURL", var (params));
  381. }
  382. void goBack() { CommandReceiver::sendCommand (outChannel, "goBack", var()); }
  383. void goForward() { CommandReceiver::sendCommand (outChannel, "goForward", var()); }
  384. void refresh() { CommandReceiver::sendCommand (outChannel, "refresh", var()); }
  385. void stop() { CommandReceiver::sendCommand (outChannel, "stop", var()); }
  386. void resized()
  387. {
  388. if (xembed != nullptr)
  389. xembed->setBounds (owner.getLocalBounds());
  390. }
  391. private:
  392. //==============================================================================
  393. void killChild()
  394. {
  395. if (childProcess != 0)
  396. {
  397. xembed = nullptr;
  398. int status = 0, result;
  399. result = waitpid (childProcess, &status, WNOHANG);
  400. for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
  401. {
  402. Thread::sleep (100);
  403. result = waitpid (childProcess, &status, WNOHANG);
  404. }
  405. // clean-up any zombies
  406. status = 0;
  407. if (! WIFEXITED(status) || result != childProcess)
  408. {
  409. do
  410. {
  411. kill (childProcess, SIGTERM);
  412. waitpid (childProcess, &status, 0);
  413. } while (! WIFEXITED(status));
  414. }
  415. childProcess = 0;
  416. }
  417. }
  418. void launchChild()
  419. {
  420. int ret;
  421. int inPipe[2], outPipe[2];
  422. ret = pipe (inPipe);
  423. ignoreUnused (ret); jassert (ret == 0);
  424. ret = pipe (outPipe);
  425. ignoreUnused (ret); jassert (ret == 0);
  426. int pid = fork();
  427. if (pid == 0)
  428. {
  429. close (inPipe[0]);
  430. close (outPipe[1]);
  431. HeapBlock<const char*> argv (5);
  432. StringArray arguments;
  433. arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
  434. arguments.add ("--juce-gtkwebkitfork-child");
  435. arguments.add (String (outPipe[0]));
  436. arguments.add (String (inPipe [1]));
  437. for (int i = 0; i < arguments.size(); ++i)
  438. argv[i] = arguments[i].toRawUTF8();
  439. argv[4] = nullptr;
  440. #if JUCE_STANDALONE_APPLICATION
  441. execv (arguments[0].toRawUTF8(), (char**) argv.getData());
  442. #else
  443. juce_gtkWebkitMain (4, (const char**) argv.getData());
  444. #endif
  445. exit (0);
  446. }
  447. close (inPipe[1]);
  448. close (outPipe[0]);
  449. inChannel = inPipe[0];
  450. outChannel = outPipe[1];
  451. childProcess = pid;
  452. }
  453. void run() override
  454. {
  455. while (! threadShouldExit())
  456. {
  457. if (shouldExit())
  458. return;
  459. receiver->tryNextRead();
  460. fd_set set;
  461. FD_ZERO (&set);
  462. FD_SET (threadControl[0], &set);
  463. FD_SET (receiver->getFd(), &set);
  464. int max_fd = jmax (threadControl[0], receiver->getFd());
  465. int result = 0;
  466. while (result == 0 || (result < 0 && errno == EINTR))
  467. result = select (max_fd + 1, &set, NULL, NULL, NULL);
  468. if (result < 0)
  469. break;
  470. }
  471. }
  472. bool shouldExit()
  473. {
  474. char ignore;
  475. ssize_t result = read (threadControl[0], &ignore, 1);
  476. return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
  477. }
  478. //==============================================================================
  479. void handleCommandOnMessageThread (const String& cmd, const var& params)
  480. {
  481. String url (params.getProperty ("url", var()).toString());
  482. if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
  483. else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
  484. else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
  485. else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
  486. else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
  487. threadBlocker.signal();
  488. }
  489. void handlePageAboutToLoad (const String& url, const var& inputParams)
  490. {
  491. int64 decision_id = inputParams.getProperty ("decision_id", var (0));
  492. if (decision_id != 0)
  493. {
  494. DynamicObject::Ptr params = new DynamicObject;
  495. params->setProperty ("decision_id", decision_id);
  496. params->setProperty ("allow", owner.pageAboutToLoad (url));
  497. CommandReceiver::sendCommand (outChannel, "decision", var (params));
  498. }
  499. }
  500. void handlePageLoadHadNetworkError (const var& params)
  501. {
  502. String error = params.getProperty ("error", "Unknown error");
  503. if (owner.pageLoadHadNetworkError (error))
  504. goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
  505. }
  506. void handleCommand (const String& cmd, const var& params) override
  507. {
  508. threadBlocker.reset();
  509. (new HandleOnMessageThread (this, cmd, params))->post();
  510. // wait until the command has executed on the message thread
  511. // this ensures that Pimpl can never be deleted while the
  512. // message has not been executed yet
  513. threadBlocker.wait (-1);
  514. }
  515. void receiverHadError() override {}
  516. //==============================================================================
  517. struct HandleOnMessageThread : public CallbackMessage
  518. {
  519. HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
  520. : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
  521. {}
  522. void messageCallback() override
  523. {
  524. owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
  525. }
  526. Pimpl* owner;
  527. String cmdToSend;
  528. var paramsToSend;
  529. };
  530. private:
  531. WebBrowserComponent& owner;
  532. ScopedPointer<CommandReceiver> receiver;
  533. int childProcess = 0, inChannel = 0, outChannel = 0;
  534. int threadControl[2];
  535. ScopedPointer<XEmbedComponent> xembed;
  536. WaitableEvent threadBlocker;
  537. };
  538. //==============================================================================
  539. WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
  540. : browser (new Pimpl (*this)),
  541. blankPageShown (false),
  542. unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
  543. {
  544. setOpaque (true);
  545. browser->init();
  546. }
  547. WebBrowserComponent::~WebBrowserComponent()
  548. {
  549. if (browser != nullptr)
  550. {
  551. delete browser;
  552. browser = nullptr;
  553. }
  554. }
  555. //==============================================================================
  556. void WebBrowserComponent::goToURL (const String& url,
  557. const StringArray* headers,
  558. const MemoryBlock* postData)
  559. {
  560. lastURL = url;
  561. if (headers != nullptr)
  562. lastHeaders = *headers;
  563. else
  564. lastHeaders.clear();
  565. if (postData != nullptr)
  566. lastPostData = *postData;
  567. else
  568. lastPostData.reset();
  569. blankPageShown = false;
  570. browser->goToURL (url, headers, postData);
  571. }
  572. void WebBrowserComponent::stop()
  573. {
  574. browser->stop();
  575. }
  576. void WebBrowserComponent::goBack()
  577. {
  578. lastURL.clear();
  579. blankPageShown = false;
  580. browser->goBack();
  581. }
  582. void WebBrowserComponent::goForward()
  583. {
  584. lastURL.clear();
  585. browser->goForward();
  586. }
  587. void WebBrowserComponent::refresh()
  588. {
  589. browser->refresh();
  590. }
  591. //==============================================================================
  592. void WebBrowserComponent::paint (Graphics& g)
  593. {
  594. g.fillAll (Colours::white);
  595. }
  596. void WebBrowserComponent::checkWindowAssociation()
  597. {
  598. }
  599. void WebBrowserComponent::reloadLastURL()
  600. {
  601. if (lastURL.isNotEmpty())
  602. {
  603. goToURL (lastURL, &lastHeaders, &lastPostData);
  604. lastURL.clear();
  605. }
  606. }
  607. void WebBrowserComponent::parentHierarchyChanged()
  608. {
  609. checkWindowAssociation();
  610. }
  611. void WebBrowserComponent::resized()
  612. {
  613. if (browser != nullptr)
  614. browser->resized();
  615. }
  616. void WebBrowserComponent::visibilityChanged()
  617. {
  618. checkWindowAssociation();
  619. }
  620. void WebBrowserComponent::focusGained (FocusChangeType)
  621. {
  622. }
  623. void WebBrowserComponent::clearCookies()
  624. {
  625. // Currently not implemented on linux as WebBrowserComponent currently does not
  626. // store cookies on linux
  627. jassertfalse;
  628. }
  629. int juce_gtkWebkitMain (int argc, const char* argv[])
  630. {
  631. if (argc != 4) return -1;
  632. GtkChildProcess child (String (argv[2]).getIntValue(),
  633. String (argv[3]).getIntValue());
  634. return child.entry();
  635. }