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