1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 //==============================================================================
30 class WebKitSymbols  : public DeletedAtShutdown
31 {
32 public:
33     //==============================================================================
isWebKitAvailable() const34     bool isWebKitAvailable() const noexcept  { return webKitIsAvailable; }
35 
36     //==============================================================================
37     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_new, juce_webkit_settings_new,
38                                          (), WebKitSettings*)
39 
40     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_hardware_acceleration_policy, juce_webkit_settings_set_hardware_acceleration_policy,
41                                          (WebKitSettings*, int), void)
42 
43     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_new_with_settings, juce_webkit_web_view_new_with_settings,
44                                          (WebKitSettings*), GtkWidget*)
45 
46     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_load_uri, juce_webkit_web_view_load_uri,
47                                          (WebKitWebView*, const gchar*), void)
48 
49     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_use, juce_webkit_policy_decision_use,
50                                          (WebKitPolicyDecision*), void)
51 
52     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_ignore, juce_webkit_policy_decision_ignore,
53                                          (WebKitPolicyDecision*), void)
54 
55     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_back, juce_webkit_web_view_go_back,
56                                          (WebKitWebView*), void)
57 
58     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_forward, juce_webkit_web_view_go_forward,
59                                          (WebKitWebView*), void)
60 
61     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_reload, juce_webkit_web_view_reload,
62                                          (WebKitWebView*), void)
63 
64     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_stop_loading, juce_webkit_web_view_stop_loading,
65                                          (WebKitWebView*), void)
66 
67     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_request_get_uri, juce_webkit_uri_request_get_uri,
68                                          (WebKitURIRequest*), const gchar*)
69 
70     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_action_get_request, juce_webkit_navigation_action_get_request,
71                                          (WebKitNavigationAction*), WebKitURIRequest*)
72 
73     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_frame_name, juce_webkit_navigation_policy_decision_get_frame_name,
74                                          (WebKitNavigationPolicyDecision*), const gchar*)
75 
76     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_navigation_action, juce_webkit_navigation_policy_decision_get_navigation_action,
77                                          (WebKitNavigationPolicyDecision*), WebKitNavigationAction*)
78 
79     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_uri, juce_webkit_web_view_get_uri,
80                                          (WebKitWebView*), const gchar*)
81 
82     //==============================================================================
83     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_init, juce_gtk_init,
84                                          (int*, char***), void)
85 
86     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_new, juce_gtk_plug_new,
87                                          (::Window), GtkWidget*)
88 
89     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_scrolled_window_new, juce_gtk_scrolled_window_new,
90                                          (GtkAdjustment*, GtkAdjustment*), GtkWidget*)
91 
92     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_container_add, juce_gtk_container_add,
93                                          (GtkContainer*, GtkWidget*), void)
94 
95     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_widget_show_all, juce_gtk_widget_show_all,
96                                          (GtkWidget*), void)
97 
98     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_get_id, juce_gtk_plug_get_id,
99                                          (GtkPlug*), ::Window)
100 
101     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main, juce_gtk_main,
102                                          (), void)
103 
104     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main_quit, juce_gtk_main_quit,
105                                          (), void)
106 
107     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_unix_fd_add, juce_g_unix_fd_add,
108                                          (gint, GIOCondition, GUnixFDSourceFunc, gpointer), guint)
109 
110     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_ref, juce_g_object_ref,
111                                          (gpointer), gpointer)
112 
113     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_unref, juce_g_object_unref,
114                                          (gpointer), void)
115 
116     JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_signal_connect_data, juce_g_signal_connect_data,
117                                          (gpointer, const gchar*, GCallback, gpointer, GClosureNotify, GConnectFlags), gulong)
118 
119     //==============================================================================
120     JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WebKitSymbols)
121 
122 private:
123     WebKitSymbols() = default;
124 
~WebKitSymbols()125     ~WebKitSymbols()
126     {
127         clearSingletonInstance();
128     }
129 
130     template<typename FuncPtr>
131     struct SymbolBinding
132     {
133         FuncPtr& func;
134         const char* name;
135     };
136 
137     template<typename FuncPtr>
makeSymbolBinding(FuncPtr & func,const char * name)138     SymbolBinding<FuncPtr> makeSymbolBinding (FuncPtr& func, const char* name)
139     {
140         return { func, name };
141     }
142 
143     template <typename FuncPtr>
loadSymbols(DynamicLibrary & lib,SymbolBinding<FuncPtr> binding)144     bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding)
145     {
146         if (auto* func = lib.getFunction (binding.name))
147         {
148             binding.func = reinterpret_cast<FuncPtr> (func);
149             return true;
150         }
151 
152         return false;
153     }
154 
155     template<typename FuncPtr, typename... Args>
loadSymbols(DynamicLibrary & lib,SymbolBinding<FuncPtr> binding,Args...args)156     bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding, Args... args)
157     {
158         return loadSymbols (lib, binding) && loadSymbols (lib, args...);
159     }
160 
161     //==============================================================================
loadWebkitSymbols()162     bool loadWebkitSymbols()
163     {
164         return loadSymbols (webkitLib,
165                             makeSymbolBinding (juce_webkit_settings_new,                                     "webkit_settings_new"),
166                             makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy,        "webkit_settings_set_hardware_acceleration_policy"),
167                             makeSymbolBinding (juce_webkit_web_view_new_with_settings,                       "webkit_web_view_new_with_settings"),
168                             makeSymbolBinding (juce_webkit_policy_decision_use,                              "webkit_policy_decision_use"),
169                             makeSymbolBinding (juce_webkit_policy_decision_ignore,                           "webkit_policy_decision_ignore"),
170                             makeSymbolBinding (juce_webkit_web_view_go_back,                                 "webkit_web_view_go_back"),
171                             makeSymbolBinding (juce_webkit_web_view_go_forward,                              "webkit_web_view_go_forward"),
172                             makeSymbolBinding (juce_webkit_web_view_reload,                                  "webkit_web_view_reload"),
173                             makeSymbolBinding (juce_webkit_web_view_stop_loading,                            "webkit_web_view_stop_loading"),
174                             makeSymbolBinding (juce_webkit_uri_request_get_uri,                              "webkit_uri_request_get_uri"),
175                             makeSymbolBinding (juce_webkit_web_view_load_uri,                                "webkit_web_view_load_uri"),
176                             makeSymbolBinding (juce_webkit_navigation_action_get_request,                    "webkit_navigation_action_get_request"),
177                             makeSymbolBinding (juce_webkit_navigation_policy_decision_get_frame_name,        "webkit_navigation_policy_decision_get_frame_name"),
178                             makeSymbolBinding (juce_webkit_navigation_policy_decision_get_navigation_action, "webkit_navigation_policy_decision_get_navigation_action"),
179                             makeSymbolBinding (juce_webkit_web_view_get_uri,                                 "webkit_web_view_get_uri"));
180     }
181 
loadGtkSymbols()182     bool loadGtkSymbols()
183     {
184         return loadSymbols (gtkLib,
185                             makeSymbolBinding (juce_gtk_init,                "gtk_init"),
186                             makeSymbolBinding (juce_gtk_plug_new,            "gtk_plug_new"),
187                             makeSymbolBinding (juce_gtk_scrolled_window_new, "gtk_scrolled_window_new"),
188                             makeSymbolBinding (juce_gtk_container_add,       "gtk_container_add"),
189                             makeSymbolBinding (juce_gtk_widget_show_all,     "gtk_widget_show_all"),
190                             makeSymbolBinding (juce_gtk_plug_get_id,         "gtk_plug_get_id"),
191                             makeSymbolBinding (juce_gtk_main,                "gtk_main"),
192                             makeSymbolBinding (juce_gtk_main_quit,           "gtk_main_quit"),
193                             makeSymbolBinding (juce_g_unix_fd_add,           "g_unix_fd_add"),
194                             makeSymbolBinding (juce_g_object_ref,            "g_object_ref"),
195                             makeSymbolBinding (juce_g_object_unref,          "g_object_unref"),
196                             makeSymbolBinding (juce_g_signal_connect_data,   "g_signal_connect_data"));
197     }
198 
199     //==============================================================================
200     DynamicLibrary gtkLib { "libgtk-3.so" }, webkitLib { "libwebkit2gtk-4.0.so" };
201     const bool webKitIsAvailable = loadWebkitSymbols() && loadGtkSymbols();
202 
203     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebKitSymbols)
204 };
205 
206 JUCE_IMPLEMENT_SINGLETON (WebKitSymbols)
207 
208 //==============================================================================
209 extern int juce_gtkWebkitMain (int argc, const char* argv[]);
210 
211 class CommandReceiver
212 {
213 public:
214     struct Responder
215     {
~Responderjuce::CommandReceiver::Responder216         virtual ~Responder() {}
217 
218         virtual void handleCommand (const String& cmd, const var& param) = 0;
219         virtual void receiverHadError() = 0;
220     };
221 
CommandReceiver(Responder * responderToUse,int inputChannelToUse)222     CommandReceiver (Responder* responderToUse, int inputChannelToUse)
223         : responder (responderToUse), inChannel (inputChannelToUse)
224     {
225         setBlocking (inChannel, false);
226     }
227 
setBlocking(int fd,bool shouldBlock)228     static void setBlocking (int fd, bool shouldBlock)
229     {
230         auto flags = fcntl (fd, F_GETFL);
231         fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
232                                          : (flags | O_NONBLOCK)));
233     }
234 
getFd() const235     int getFd() const     { return inChannel; }
236 
tryNextRead()237     void tryNextRead()
238     {
239         for (;;)
240         {
241             auto len = (receivingLength ? sizeof (size_t) : bufferLength.len);
242 
243             if (! receivingLength)
244                 buffer.realloc (len);
245 
246             auto* dst = (receivingLength ? bufferLength.data : buffer.getData());
247 
248             auto actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
249 
250             if (actual < 0)
251             {
252                 if (errno == EINTR)
253                     continue;
254 
255                 break;
256             }
257 
258             pos += static_cast<size_t> (actual);
259 
260             if (pos == len)
261             {
262                 pos = 0;
263 
264                 if (! receivingLength)
265                     parseJSON (String (buffer.getData(), bufferLength.len));
266 
267                 receivingLength = (! receivingLength);
268             }
269         }
270 
271         if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
272             responder->receiverHadError();
273     }
274 
sendCommand(int outChannel,const String & cmd,const var & params)275     static void sendCommand (int outChannel, const String& cmd, const var& params)
276     {
277         DynamicObject::Ptr obj = new DynamicObject;
278 
279         obj->setProperty (getCmdIdentifier(), cmd);
280 
281         if (! params.isVoid())
282             obj->setProperty (getParamIdentifier(), params);
283 
284         auto json = JSON::toString (var (obj.get()));
285 
286         auto jsonLength = static_cast<size_t> (json.length());
287         auto len        = sizeof (size_t) + jsonLength;
288 
289         HeapBlock<char> buffer (len);
290         auto* dst = buffer.getData();
291 
292         memcpy (dst, &jsonLength, sizeof (size_t));
293         dst += sizeof (size_t);
294 
295         memcpy (dst, json.toRawUTF8(), jsonLength);
296 
297         ssize_t ret;
298 
299         for (;;)
300         {
301             ret = write (outChannel, buffer.getData(), len);
302 
303             if (ret != -1 || errno != EINTR)
304                 break;
305         }
306     }
307 
308 private:
parseJSON(const String & json)309     void parseJSON (const String& json)
310     {
311         auto object = JSON::fromString (json);
312 
313         if (! object.isVoid())
314         {
315             auto cmd    = object.getProperty (getCmdIdentifier(),   {}).toString();
316             auto params = object.getProperty (getParamIdentifier(), {});
317 
318             if (responder != nullptr)
319                 responder->handleCommand (cmd, params);
320         }
321     }
322 
getCmdIdentifier()323     static Identifier getCmdIdentifier()    { static Identifier Id ("cmd");    return Id; }
getParamIdentifier()324     static Identifier getParamIdentifier()  { static Identifier Id ("params"); return Id; }
325 
326     Responder* responder = nullptr;
327     int inChannel = 0;
328     size_t pos = 0;
329     bool receivingLength = true;
330     union { char data [sizeof (size_t)]; size_t len; } bufferLength;
331     HeapBlock<char> buffer;
332 };
333 
334 #define juce_g_signal_connect(instance, detailed_signal, c_handler, data) \
335     WebKitSymbols::getInstance()->juce_g_signal_connect_data (instance, detailed_signal, c_handler, data, nullptr, (GConnectFlags) 0)
336 
337 //==============================================================================
338 class GtkChildProcess : private CommandReceiver::Responder
339 {
340 public:
341     //==============================================================================
GtkChildProcess(int inChannel,int outChannelToUse)342     GtkChildProcess (int inChannel, int outChannelToUse)
343         : outChannel (outChannelToUse),
344           receiver (this, inChannel)
345     {}
346 
entry()347     int entry()
348     {
349         CommandReceiver::setBlocking (outChannel, true);
350 
351         WebKitSymbols::getInstance()->juce_gtk_init (nullptr, nullptr);
352 
353         auto* settings = WebKitSymbols::getInstance()->juce_webkit_settings_new();
354         WebKitSymbols::getInstance()->juce_webkit_settings_set_hardware_acceleration_policy (settings,
355                                                                                              /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */ 2);
356 
357         auto* plug      = WebKitSymbols::getInstance()->juce_gtk_plug_new (0);
358         auto* container = WebKitSymbols::getInstance()->juce_gtk_scrolled_window_new (nullptr, nullptr);
359 
360         auto* webviewWidget = WebKitSymbols::getInstance()->juce_webkit_web_view_new_with_settings (settings);
361         webview = (WebKitWebView*) webviewWidget;
362 
363         WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) container, webviewWidget);
364         WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) plug,      container);
365 
366         WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, "about:blank");
367 
368         juce_g_signal_connect (webview, "decide-policy",
369                                (GCallback) decidePolicyCallback, this);
370 
371         juce_g_signal_connect (webview, "load-changed",
372                                (GCallback) loadChangedCallback, this);
373 
374         juce_g_signal_connect (webview, "load-failed",
375                                (GCallback) loadFailedCallback, this);
376 
377         WebKitSymbols::getInstance()->juce_gtk_widget_show_all (plug);
378         auto wID = (unsigned long) WebKitSymbols::getInstance()->juce_gtk_plug_get_id ((GtkPlug*) plug);
379 
380         ssize_t ret;
381 
382         for (;;)
383         {
384             ret = write (outChannel, &wID, sizeof (wID));
385 
386             if (ret != -1 || errno != EINTR)
387                 break;
388         }
389 
390         WebKitSymbols::getInstance()->juce_g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
391         receiver.tryNextRead();
392 
393         WebKitSymbols::getInstance()->juce_gtk_main();
394 
395         WebKitSymbols::getInstance()->deleteInstance();
396         return 0;
397     }
398 
goToURL(const var & params)399     void goToURL (const var& params)
400     {
401         static Identifier urlIdentifier ("url");
402         auto url = params.getProperty (urlIdentifier, var()).toString();
403 
404         WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, url.toRawUTF8());
405     }
406 
handleDecisionResponse(const var & params)407     void handleDecisionResponse (const var& params)
408     {
409         auto* decision = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
410         bool allow = params.getProperty ("allow", var (false));
411 
412         if (decision != nullptr && decisions.contains (decision))
413         {
414             if (allow)
415                 WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
416             else
417                 WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision);
418 
419             decisions.removeAllInstancesOf (decision);
420             WebKitSymbols::getInstance()->juce_g_object_unref (decision);
421         }
422     }
423 
424     //==============================================================================
handleCommand(const String & cmd,const var & params)425     void handleCommand (const String& cmd, const var& params) override
426     {
427         if      (cmd == "quit")      quit();
428         else if (cmd == "goToURL")   goToURL (params);
429         else if (cmd == "goBack")    WebKitSymbols::getInstance()->juce_webkit_web_view_go_back      (webview);
430         else if (cmd == "goForward") WebKitSymbols::getInstance()->juce_webkit_web_view_go_forward   (webview);
431         else if (cmd == "refresh")   WebKitSymbols::getInstance()->juce_webkit_web_view_reload       (webview);
432         else if (cmd == "stop")      WebKitSymbols::getInstance()->juce_webkit_web_view_stop_loading (webview);
433         else if (cmd == "decision")  handleDecisionResponse (params);
434     }
435 
receiverHadError()436     void receiverHadError() override
437     {
438         exit (-1);
439     }
440 
441     //==============================================================================
pipeReady(gint fd,GIOCondition)442     bool pipeReady (gint fd, GIOCondition)
443     {
444         if (fd == receiver.getFd())
445         {
446             receiver.tryNextRead();
447             return true;
448         }
449 
450         return false;
451     }
452 
quit()453     void quit()
454     {
455         WebKitSymbols::getInstance()->juce_gtk_main_quit();
456     }
457 
getURIStringForAction(WebKitNavigationAction * action)458     String getURIStringForAction (WebKitNavigationAction* action)
459     {
460         auto* request = WebKitSymbols::getInstance()->juce_webkit_navigation_action_get_request (action);
461         return WebKitSymbols::getInstance()->juce_webkit_uri_request_get_uri (request);
462     }
463 
onNavigation(String frameName,WebKitNavigationAction * action,WebKitPolicyDecision * decision)464     bool onNavigation (String frameName,
465                        WebKitNavigationAction* action,
466                        WebKitPolicyDecision* decision)
467     {
468         if (decision != nullptr && frameName.isEmpty())
469         {
470             WebKitSymbols::getInstance()->juce_g_object_ref (decision);
471             decisions.add (decision);
472 
473             DynamicObject::Ptr params = new DynamicObject;
474 
475             params->setProperty ("url", getURIStringForAction (action));
476             params->setProperty ("decision_id", (int64) decision);
477             CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params.get()));
478 
479             return true;
480         }
481 
482         return false;
483     }
484 
onNewWindow(String,WebKitNavigationAction * action,WebKitPolicyDecision * decision)485     bool onNewWindow (String /*frameName*/,
486                       WebKitNavigationAction* action,
487                       WebKitPolicyDecision* decision)
488     {
489         if (decision != nullptr)
490         {
491             DynamicObject::Ptr params = new DynamicObject;
492 
493             params->setProperty ("url", getURIStringForAction (action));
494             CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params.get()));
495 
496             // never allow new windows
497             WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision);
498 
499             return true;
500         }
501 
502         return false;
503     }
504 
onLoadChanged(WebKitLoadEvent loadEvent)505     void onLoadChanged (WebKitLoadEvent loadEvent)
506     {
507         if (loadEvent == WEBKIT_LOAD_FINISHED)
508         {
509             DynamicObject::Ptr params = new DynamicObject;
510 
511             params->setProperty ("url", String (WebKitSymbols::getInstance()->juce_webkit_web_view_get_uri (webview)));
512             CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params.get()));
513         }
514     }
515 
onDecidePolicy(WebKitPolicyDecision * decision,WebKitPolicyDecisionType decisionType)516     bool onDecidePolicy (WebKitPolicyDecision*    decision,
517                          WebKitPolicyDecisionType decisionType)
518     {
519         switch (decisionType)
520         {
521         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
522             {
523                 auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision;
524                 auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision);
525 
526                 return onNavigation (String (frameName != nullptr ? frameName : ""),
527                                      WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
528                                      decision);
529             }
530             break;
531         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
532             {
533                 auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision;
534                 auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision);
535 
536                 return onNewWindow  (String (frameName != nullptr ? frameName : ""),
537                                      WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
538                                      decision);
539             }
540             break;
541         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
542             {
543                 auto* response = (WebKitNavigationPolicyDecision*) decision;
544 
545                 // for now just always allow response requests
546                 ignoreUnused (response);
547                 WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
548                 return true;
549             }
550             break;
551         default:
552             break;
553         }
554 
555         return false;
556     }
557 
onLoadFailed(GError * error)558     void onLoadFailed (GError* error)
559     {
560         DynamicObject::Ptr params = new DynamicObject;
561 
562         params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
563         CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params.get()));
564     }
565 
566 private:
pipeReadyStatic(gint fd,GIOCondition condition,gpointer user)567     static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
568     {
569         return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
570     }
571 
decidePolicyCallback(WebKitWebView *,WebKitPolicyDecision * decision,WebKitPolicyDecisionType decisionType,gpointer user)572     static gboolean decidePolicyCallback (WebKitWebView*,
573                                           WebKitPolicyDecision*    decision,
574                                           WebKitPolicyDecisionType decisionType,
575                                           gpointer user)
576     {
577         auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
578         return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
579     }
580 
loadChangedCallback(WebKitWebView *,WebKitLoadEvent loadEvent,gpointer user)581     static void loadChangedCallback (WebKitWebView*,
582                                      WebKitLoadEvent loadEvent,
583                                      gpointer        user)
584     {
585         auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
586         owner.onLoadChanged (loadEvent);
587     }
588 
loadFailedCallback(WebKitWebView *,WebKitLoadEvent,gchar *,GError * error,gpointer user)589     static void loadFailedCallback (WebKitWebView*,
590                                     WebKitLoadEvent /*loadEvent*/,
591                                     gchar*          /*failing_uri*/,
592                                     GError*         error,
593                                     gpointer        user)
594     {
595         auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
596         owner.onLoadFailed (error);
597     }
598 
599     int outChannel = 0;
600     CommandReceiver receiver;
601     WebKitWebView* webview = nullptr;
602     Array<WebKitPolicyDecision*> decisions;
603 };
604 
605 //==============================================================================
606 class WebBrowserComponent::Pimpl  : private Thread,
607                                     private CommandReceiver::Responder
608 {
609 public:
Pimpl(WebBrowserComponent & parent)610     Pimpl (WebBrowserComponent& parent)
611         : Thread ("Webview"), owner (parent)
612     {
613         webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable();
614     }
615 
~Pimpl()616     ~Pimpl() override
617     {
618         quit();
619     }
620 
621     //==============================================================================
init()622     void init()
623     {
624         if (! webKitIsAvailable)
625             return;
626 
627         launchChild();
628 
629         auto ret = pipe (threadControl);
630 
631         ignoreUnused (ret);
632         jassert (ret == 0);
633 
634         CommandReceiver::setBlocking (inChannel,        true);
635         CommandReceiver::setBlocking (outChannel,       true);
636         CommandReceiver::setBlocking (threadControl[0], false);
637         CommandReceiver::setBlocking (threadControl[1], true);
638 
639         unsigned long windowHandle;
640         auto actual = read (inChannel, &windowHandle, sizeof (windowHandle));
641 
642         if (actual != (ssize_t) sizeof (windowHandle))
643         {
644             killChild();
645             return;
646         }
647 
648         receiver.reset (new CommandReceiver (this, inChannel));
649 
650         pfds.push_back ({ threadControl[0],  POLLIN, 0 });
651         pfds.push_back ({ receiver->getFd(), POLLIN, 0 });
652 
653         startThread();
654 
655         xembed.reset (new XEmbedComponent (windowHandle));
656         owner.addAndMakeVisible (xembed.get());
657     }
658 
quit()659     void quit()
660     {
661         if (! webKitIsAvailable)
662             return;
663 
664         if (isThreadRunning())
665         {
666             signalThreadShouldExit();
667 
668             char ignore = 0;
669             ssize_t ret;
670 
671             for (;;)
672             {
673                 ret = write (threadControl[1], &ignore, 1);
674 
675                 if (ret != -1 || errno != EINTR)
676                     break;
677             }
678 
679             waitForThreadToExit (-1);
680             receiver = nullptr;
681         }
682 
683         if (childProcess != 0)
684         {
685             CommandReceiver::sendCommand (outChannel, "quit", {});
686             killChild();
687         }
688     }
689 
690     //==============================================================================
goToURL(const String & url,const StringArray * headers,const MemoryBlock * postData)691     void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
692     {
693         if (! webKitIsAvailable)
694             return;
695 
696         DynamicObject::Ptr params = new DynamicObject;
697 
698         params->setProperty ("url", url);
699 
700         if (headers != nullptr)
701             params->setProperty ("headers", var (*headers));
702 
703         if (postData != nullptr)
704             params->setProperty ("postData", var (*postData));
705 
706         CommandReceiver::sendCommand (outChannel, "goToURL", var (params.get()));
707     }
708 
goBack()709     void goBack()      { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack",    {}); }
goForward()710     void goForward()   { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); }
refresh()711     void refresh()     { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh",   {}); }
stop()712     void stop()        { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop",      {}); }
713 
resized()714     void resized()
715     {
716         if (xembed != nullptr)
717             xembed->setBounds (owner.getLocalBounds());
718     }
719 
720 private:
721     //==============================================================================
killChild()722     void killChild()
723     {
724         if (childProcess != 0)
725         {
726             xembed = nullptr;
727 
728             int status = 0, result = 0;
729 
730             result = waitpid (childProcess, &status, WNOHANG);
731             for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
732             {
733                 Thread::sleep (100);
734                 result = waitpid (childProcess, &status, WNOHANG);
735             }
736 
737             // clean-up any zombies
738             status = 0;
739             if (! WIFEXITED(status) || result != childProcess)
740             {
741                 for (;;)
742                 {
743                     kill (childProcess, SIGTERM);
744                     waitpid (childProcess, &status, 0);
745 
746                     if (WIFEXITED (status))
747                         break;
748                 }
749             }
750 
751             childProcess = 0;
752         }
753     }
754 
launchChild()755     void launchChild()
756     {
757         int inPipe[2], outPipe[2];
758 
759         auto ret = pipe (inPipe);
760         ignoreUnused (ret); jassert (ret == 0);
761 
762         ret = pipe (outPipe);
763         ignoreUnused (ret); jassert (ret == 0);
764 
765         auto pid = fork();
766         if (pid == 0)
767         {
768             close (inPipe[0]);
769             close (outPipe[1]);
770 
771             HeapBlock<const char*> argv (5);
772             StringArray arguments;
773 
774             arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
775             arguments.add ("--juce-gtkwebkitfork-child");
776             arguments.add (String (outPipe[0]));
777             arguments.add (String (inPipe [1]));
778 
779             for (int i = 0; i < arguments.size(); ++i)
780                 argv[i] = arguments[i].toRawUTF8();
781 
782             argv[4] = nullptr;
783 
784            #if JUCE_STANDALONE_APPLICATION
785             execv (arguments[0].toRawUTF8(), (char**) argv.getData());
786            #else
787             juce_gtkWebkitMain (4, (const char**) argv.getData());
788            #endif
789             exit (0);
790         }
791 
792         close (inPipe[1]);
793         close (outPipe[0]);
794 
795         inChannel  = inPipe[0];
796         outChannel = outPipe[1];
797 
798         childProcess = pid;
799     }
800 
run()801     void run() override
802     {
803         while (! threadShouldExit())
804         {
805             if (shouldExit())
806                 return;
807 
808             receiver->tryNextRead();
809 
810             int result = 0;
811 
812             while (result == 0 || (result < 0 && errno == EINTR))
813                 result = poll (&pfds.front(), static_cast<nfds_t> (pfds.size()), 0);
814 
815             if (result < 0)
816                 break;
817         }
818     }
819 
shouldExit()820     bool shouldExit()
821     {
822         char ignore;
823         auto result = read (threadControl[0], &ignore, 1);
824 
825         return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
826     }
827 
828     //==============================================================================
handleCommandOnMessageThread(const String & cmd,const var & params)829     void handleCommandOnMessageThread (const String& cmd, const var& params)
830     {
831         auto url = params.getProperty ("url", var()).toString();
832 
833         if      (cmd == "pageAboutToLoad")           handlePageAboutToLoad (url, params);
834         else if (cmd == "pageFinishedLoading")       owner.pageFinishedLoading (url);
835         else if (cmd == "windowCloseRequest")        owner.windowCloseRequest();
836         else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
837         else if (cmd == "pageLoadHadNetworkError")   handlePageLoadHadNetworkError (params);
838 
839         threadBlocker.signal();
840     }
841 
handlePageAboutToLoad(const String & url,const var & inputParams)842     void handlePageAboutToLoad (const String& url, const var& inputParams)
843     {
844         int64 decision_id = inputParams.getProperty ("decision_id", var (0));
845 
846         if (decision_id != 0)
847         {
848             DynamicObject::Ptr params = new DynamicObject;
849 
850             params->setProperty ("decision_id", decision_id);
851             params->setProperty ("allow", owner.pageAboutToLoad (url));
852 
853             CommandReceiver::sendCommand (outChannel, "decision", var (params.get()));
854         }
855     }
856 
handlePageLoadHadNetworkError(const var & params)857     void handlePageLoadHadNetworkError (const var& params)
858     {
859         String error = params.getProperty ("error", "Unknown error");
860 
861         if (owner.pageLoadHadNetworkError (error))
862             goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
863     }
864 
handleCommand(const String & cmd,const var & params)865     void handleCommand (const String& cmd, const var& params) override
866     {
867         threadBlocker.reset();
868 
869         (new HandleOnMessageThread (this, cmd, params))->post();
870 
871         // wait until the command has executed on the message thread
872         // this ensures that Pimpl can never be deleted while the
873         // message has not been executed yet
874         threadBlocker.wait (-1);
875     }
876 
receiverHadError()877     void receiverHadError() override {}
878 
879     //==============================================================================
880     struct HandleOnMessageThread : public CallbackMessage
881     {
HandleOnMessageThreadjuce::WebBrowserComponent::Pimpl::HandleOnMessageThread882         HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
883             : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
884         {}
885 
messageCallbackjuce::WebBrowserComponent::Pimpl::HandleOnMessageThread886         void messageCallback() override
887         {
888             owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
889         }
890 
891         Pimpl* owner = nullptr;
892         String cmdToSend;
893         var paramsToSend;
894     };
895 
896     bool webKitIsAvailable = false;
897 
898     WebBrowserComponent& owner;
899     std::unique_ptr<CommandReceiver> receiver;
900     int childProcess = 0, inChannel = 0, outChannel = 0;
901     int threadControl[2];
902     std::unique_ptr<XEmbedComponent> xembed;
903     WaitableEvent threadBlocker;
904     std::vector<pollfd> pfds;
905 };
906 
907 //==============================================================================
WebBrowserComponent(const bool unloadWhenHidden)908 WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden)
909     : browser (new Pimpl (*this)),
910       unloadPageWhenBrowserIsHidden (unloadWhenHidden)
911 {
912     ignoreUnused (blankPageShown);
913     ignoreUnused (unloadPageWhenBrowserIsHidden);
914 
915     setOpaque (true);
916 
917     browser->init();
918 }
919 
WebBrowserComponent(bool unloadWhenHidden,const File &,const File &)920 WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden,
921                                           const File&,
922                                           const File&)
923     : WebBrowserComponent (unloadWhenHidden)
924 {
925 }
926 
~WebBrowserComponent()927 WebBrowserComponent::~WebBrowserComponent()
928 {
929 }
930 
931 //==============================================================================
goToURL(const String & url,const StringArray * headers,const MemoryBlock * postData)932 void WebBrowserComponent::goToURL (const String& url,
933                                    const StringArray* headers,
934                                    const MemoryBlock* postData)
935 {
936     lastURL = url;
937 
938     if (headers != nullptr)
939         lastHeaders = *headers;
940     else
941         lastHeaders.clear();
942 
943     if (postData != nullptr)
944         lastPostData = *postData;
945     else
946         lastPostData.reset();
947 
948     browser->goToURL (url, headers, postData);
949 }
950 
stop()951 void WebBrowserComponent::stop()
952 {
953     browser->stop();
954 }
955 
goBack()956 void WebBrowserComponent::goBack()
957 {
958     lastURL.clear();
959 
960     browser->goBack();
961 }
962 
goForward()963 void WebBrowserComponent::goForward()
964 {
965     lastURL.clear();
966     browser->goForward();
967 }
968 
refresh()969 void WebBrowserComponent::refresh()
970 {
971     browser->refresh();
972 }
973 
974 //==============================================================================
paint(Graphics & g)975 void WebBrowserComponent::paint (Graphics& g)
976 {
977     g.fillAll (Colours::white);
978 }
979 
checkWindowAssociation()980 void WebBrowserComponent::checkWindowAssociation()
981 {
982 }
983 
reloadLastURL()984 void WebBrowserComponent::reloadLastURL()
985 {
986     if (lastURL.isNotEmpty())
987     {
988         goToURL (lastURL, &lastHeaders, &lastPostData);
989         lastURL.clear();
990     }
991 }
992 
parentHierarchyChanged()993 void WebBrowserComponent::parentHierarchyChanged()
994 {
995     checkWindowAssociation();
996 }
997 
resized()998 void WebBrowserComponent::resized()
999 {
1000     if (browser != nullptr)
1001         browser->resized();
1002 }
1003 
visibilityChanged()1004 void WebBrowserComponent::visibilityChanged()
1005 {
1006     checkWindowAssociation();
1007 }
1008 
focusGained(FocusChangeType)1009 void WebBrowserComponent::focusGained (FocusChangeType)
1010 {
1011 }
1012 
clearCookies()1013 void WebBrowserComponent::clearCookies()
1014 {
1015     // Currently not implemented on linux as WebBrowserComponent currently does not
1016     // store cookies on linux
1017     jassertfalse;
1018 }
1019 
juce_gtkWebkitMain(int argc,const char * argv[])1020 int juce_gtkWebkitMain (int argc, const char* argv[])
1021 {
1022     if (argc != 4)
1023         return -1;
1024 
1025     GtkChildProcess child (String (argv[2]).getIntValue(),
1026                            String (argv[3]).getIntValue());
1027 
1028     return child.entry();
1029 }
1030 
1031 } // namespace juce
1032