wpe: Add a source element acting as a Web Browser based on WebKit WPE
[platform/upstream/gstreamer.git] / ext / wpe / WPEThreadedView.cpp
1 /* Copyright (C) <2018> Philippe Normand <philn@igalia.com>
2  * Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "WPEThreadedView.h"
21
22 #include <cstdio>
23 #include <mutex>
24
25 #define GST_CAT_DEFAULT wpe_src_debug
26
27 // -70 is the GLib priority we use internally in WebKit, for WPE.
28 #define WPE_GLIB_SOURCE_PRIORITY -70
29
30 class GMutexHolder {
31 public:
32     GMutexHolder(GMutex& mutex)
33         : m(mutex)
34     {
35         g_mutex_lock(&m);
36     }
37     ~GMutexHolder()
38     {
39         g_mutex_unlock(&m);
40     }
41
42 private:
43     GMutex& m;
44 };
45
46 WPEThreadedView::WPEThreadedView()
47 {
48     g_mutex_init(&threading.mutex);
49     g_cond_init(&threading.cond);
50     g_mutex_init(&threading.ready_mutex);
51     g_cond_init(&threading.ready_cond);
52
53     g_mutex_init(&images.mutex);
54
55     {
56         GMutexHolder lock(threading.mutex);
57         threading.thread = g_thread_new("WPEThreadedView",
58             s_viewThread, this);
59         g_cond_wait(&threading.cond, &threading.mutex);
60         GST_DEBUG("thread spawned");
61     }
62 }
63
64 WPEThreadedView::~WPEThreadedView()
65 {
66     {
67         GMutexHolder lock(images.mutex);
68
69         if (images.pending) {
70             gst_egl_image_unref(images.pending);
71             images.pending = nullptr;
72         }
73         if (images.committed) {
74             gst_egl_image_unref(images.committed);
75             images.committed = nullptr;
76         }
77     }
78
79     if (gst.display) {
80         gst_object_unref(gst.display);
81         gst.display = nullptr;
82     }
83
84     if (gst.context) {
85         gst_object_unref(gst.context);
86         gst.context = nullptr;
87     }
88
89     if (threading.thread) {
90         g_thread_unref(threading.thread);
91         threading.thread = nullptr;
92     }
93
94     g_mutex_clear(&threading.mutex);
95     g_cond_clear(&threading.cond);
96     g_mutex_clear(&threading.ready_mutex);
97     g_cond_clear(&threading.ready_cond);
98     g_mutex_clear(&images.mutex);
99 }
100
101 gpointer WPEThreadedView::s_viewThread(gpointer data)
102 {
103     auto& view = *static_cast<WPEThreadedView*>(data);
104
105     view.glib.context = g_main_context_new();
106     view.glib.loop = g_main_loop_new(view.glib.context, FALSE);
107
108     g_main_context_push_thread_default(view.glib.context);
109
110     {
111         GSource* source = g_idle_source_new();
112         g_source_set_callback(source,
113             [](gpointer data) -> gboolean {
114                 auto& view = *static_cast<WPEThreadedView*>(data);
115                 GMutexHolder lock(view.threading.mutex);
116                 g_cond_signal(&view.threading.cond);
117                 return G_SOURCE_REMOVE;
118             },
119             &view, nullptr);
120         g_source_attach(source, view.glib.context);
121         g_source_unref(source);
122     }
123
124     g_main_loop_run(view.glib.loop);
125
126     g_main_loop_unref(view.glib.loop);
127     view.glib.loop = nullptr;
128
129     if (view.webkit.view) {
130         g_object_unref(view.webkit.view);
131         view.webkit.view = nullptr;
132     }
133     if (view.webkit.uri) {
134         g_free(view.webkit.uri);
135         view.webkit.uri = nullptr;
136     }
137
138     g_main_context_pop_thread_default(view.glib.context);
139     g_main_context_unref(view.glib.context);
140     view.glib.context = nullptr;
141     return nullptr;
142 }
143
144 struct wpe_view_backend* WPEThreadedView::backend() const
145 {
146     return wpe.exportable ? wpe_view_backend_exportable_fdo_get_view_backend(wpe.exportable) : nullptr;
147 }
148
149 void WPEThreadedView::s_loadEvent(WebKitWebView*, WebKitLoadEvent event, gpointer data)
150 {
151     if (event == WEBKIT_LOAD_COMMITTED) {
152         auto& view = *static_cast<WPEThreadedView*>(data);
153         GMutexHolder lock(view.threading.ready_mutex);
154         g_cond_signal(&view.threading.ready_cond);
155     }
156 }
157
158 void WPEThreadedView::initialize(GstWpeSrc* src, GstGLContext* context, GstGLDisplay* display, int width, int height)
159 {
160     GST_DEBUG("context %p display %p, size (%d,%d)", context, display, width, height);
161
162     static std::once_flag s_loaderFlag;
163     std::call_once(s_loaderFlag,
164         [] {
165 #if defined(WPE_BACKEND_CHECK_VERSION) && WPE_BACKEND_CHECK_VERSION(0, 2, 0)
166             wpe_loader_init("libWPEBackend-fdo-0.1.so");
167 #endif
168         });
169
170     struct InitializeContext {
171         GstWpeSrc* src;
172         WPEThreadedView& view;
173         GstGLContext* context;
174         GstGLDisplay* display;
175         int width;
176         int height;
177     } initializeContext{ src, *this, context, display, width, height };
178
179     GSource* source = g_idle_source_new();
180     g_source_set_callback(source,
181         [](gpointer data) -> gboolean {
182             GST_DEBUG("on view thread");
183             auto& initializeContext = *static_cast<InitializeContext*>(data);
184             auto& view = initializeContext.view;
185
186             GMutexHolder lock(view.threading.mutex);
187
188             view.gst.context = GST_GL_CONTEXT(gst_object_ref(initializeContext.context));
189             view.gst.display = GST_GL_DISPLAY(gst_object_ref(initializeContext.display));
190
191             view.wpe.width = initializeContext.width;
192             view.wpe.height = initializeContext.height;
193
194             EGLDisplay eglDisplay = gst_gl_display_egl_get_from_native(
195                 GST_GL_DISPLAY_TYPE_WAYLAND,
196                 gst_gl_display_get_handle(initializeContext.display));
197             GST_DEBUG("eglDisplay %p", eglDisplay);
198             wpe_fdo_initialize_for_egl_display(eglDisplay);
199
200             view.wpe.exportable = wpe_view_backend_exportable_fdo_egl_create(&s_exportableClient,
201                 &view, view.wpe.width, view.wpe.height);
202             auto* viewBackend = webkit_web_view_backend_new(
203                 wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), nullptr, nullptr);
204
205             view.webkit.view = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
206                 "backend", viewBackend, nullptr));
207
208             gst_wpe_src_configure_web_view(initializeContext.src, view.webkit.view);
209
210             g_signal_connect(view.webkit.view, "load-changed", G_CALLBACK(s_loadEvent), &view);
211
212             const gchar* location;
213             gboolean drawBackground = TRUE;
214             g_object_get(initializeContext.src, "location", &location, "draw-background", &drawBackground, nullptr);
215             if (!location)
216                 g_warning("Invalid location");
217             else {
218                 view.setDrawBackground(drawBackground);
219                 view.loadUriUnlocked(location);
220             }
221             g_cond_signal(&view.threading.cond);
222             return G_SOURCE_REMOVE;
223         },
224         &initializeContext, nullptr);
225     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
226
227     {
228         GMutexHolder lock(threading.mutex);
229         g_source_attach(source, glib.context);
230         g_cond_wait(&threading.cond, &threading.mutex);
231     }
232
233     g_source_unref(source);
234
235     {
236         GST_DEBUG("waiting load to finish");
237         GMutexHolder lock(threading.ready_mutex);
238         g_cond_wait(&threading.ready_cond, &threading.ready_mutex);
239         GST_DEBUG("done");
240     }
241 }
242
243 GstEGLImage* WPEThreadedView::image()
244 {
245     GstEGLImage* ret = nullptr;
246     GMutexHolder lock(images.mutex);
247
248     GST_TRACE("pending %" GST_PTR_FORMAT " committed %" GST_PTR_FORMAT, images.pending, images.committed);
249
250     if (images.pending) {
251         auto* previousImage = images.committed;
252         images.committed = images.pending;
253         images.pending = nullptr;
254
255         frameComplete();
256
257         if (previousImage)
258             gst_egl_image_unref(previousImage);
259     }
260
261     if (images.committed)
262         ret = images.committed;
263
264     return ret;
265 }
266
267 void WPEThreadedView::resize(int width, int height)
268 {
269     GST_DEBUG("resize");
270
271     GSource* source = g_idle_source_new();
272     g_source_set_callback(source,
273         [](gpointer data) -> gboolean {
274             auto& view = *static_cast<WPEThreadedView*>(data);
275             GMutexHolder lock(view.threading.mutex);
276
277             GST_DEBUG("dispatching");
278             if (view.wpe.exportable && wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable))
279                 wpe_view_backend_dispatch_set_size(wpe_view_backend_exportable_fdo_get_view_backend(view.wpe.exportable), view.wpe.width, view.wpe.height);
280
281             g_cond_signal(&view.threading.cond);
282             return G_SOURCE_REMOVE;
283         },
284         this, nullptr);
285     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
286
287     {
288         GMutexHolder lock(threading.mutex);
289         g_source_attach(source, glib.context);
290         g_cond_wait(&threading.cond, &threading.mutex);
291     }
292
293     g_source_unref(source);
294 }
295
296 void WPEThreadedView::frameComplete()
297 {
298     GST_DEBUG("frame complete");
299
300     GSource* source = g_idle_source_new();
301     g_source_set_callback(source,
302         [](gpointer data) -> gboolean {
303             auto& view = *static_cast<WPEThreadedView*>(data);
304             GMutexHolder lock(view.threading.mutex);
305
306             GST_DEBUG("dispatching");
307             wpe_view_backend_exportable_fdo_dispatch_frame_complete(view.wpe.exportable);
308
309             g_cond_signal(&view.threading.cond);
310             return G_SOURCE_REMOVE;
311         },
312         this, nullptr);
313     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
314
315     {
316         GMutexHolder lock(threading.mutex);
317         g_source_attach(source, glib.context);
318         g_cond_wait(&threading.cond, &threading.mutex);
319     }
320
321     g_source_unref(source);
322 }
323
324 void WPEThreadedView::loadUriUnlocked(const gchar* uri)
325 {
326     if (webkit.uri)
327         g_free(webkit.uri);
328     webkit.uri = g_strdup(uri);
329     webkit_web_view_load_uri(webkit.view, webkit.uri);
330 }
331
332 void WPEThreadedView::loadUri(const gchar* uri)
333 {
334     GST_DEBUG("loading %s", uri);
335
336     struct UriContext {
337         WPEThreadedView& view;
338         const gchar* uri;
339     } uriContext{ *this, uri };
340
341     GSource* source = g_idle_source_new();
342     g_source_set_callback(source,
343         [](gpointer data) -> gboolean {
344             GST_DEBUG("on view thread");
345             auto& uriContext = *static_cast<UriContext*>(data);
346             auto& view = uriContext.view;
347             GMutexHolder lock(view.threading.mutex);
348
349             view.loadUriUnlocked(uriContext.uri);
350
351             g_cond_signal(&view.threading.cond);
352             return G_SOURCE_REMOVE;
353         },
354         &uriContext, nullptr);
355     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
356
357     {
358         GMutexHolder lock(threading.mutex);
359         g_source_attach(source, glib.context);
360         g_cond_wait(&threading.cond, &threading.mutex);
361         GST_DEBUG("done");
362     }
363
364     g_source_unref(source);
365 }
366
367 void WPEThreadedView::setDrawBackground(gboolean drawsBackground)
368 {
369 #if 1
370     // See https://bugs.webkit.org/show_bug.cgi?id=192305
371     GST_FIXME("set_draws_background API not upstream yet");
372 #else
373     webkit_web_view_set_draws_background(webkit.view, drawsBackground);
374 #endif
375 }
376
377 void WPEThreadedView::releaseImage(EGLImageKHR image)
378 {
379     struct ReleaseImageContext {
380         WPEThreadedView& view;
381         EGLImageKHR image;
382     } releaseImageContext{ *this, image };
383
384     GSource* source = g_idle_source_new();
385     g_source_set_callback(source,
386         [](gpointer data) -> gboolean {
387             auto& releaseImageContext = *static_cast<ReleaseImageContext*>(data);
388             auto& view = releaseImageContext.view;
389             GMutexHolder lock(view.threading.mutex);
390
391             wpe_view_backend_exportable_fdo_egl_dispatch_release_image(
392                 releaseImageContext.view.wpe.exportable, releaseImageContext.image);
393
394             g_cond_signal(&view.threading.cond);
395             return G_SOURCE_REMOVE;
396         },
397         &releaseImageContext, nullptr);
398     g_source_set_priority(source, WPE_GLIB_SOURCE_PRIORITY);
399
400     {
401         GMutexHolder lock(threading.mutex);
402         g_source_attach(source, glib.context);
403         g_cond_wait(&threading.cond, &threading.mutex);
404     }
405
406     g_source_unref(source);
407 }
408
409 struct wpe_view_backend_exportable_fdo_egl_client WPEThreadedView::s_exportableClient = {
410     // export_buffer_resource
411     [](void* data, EGLImageKHR image) {
412         auto& view = *static_cast<WPEThreadedView*>(data);
413         auto* gstImage = gst_egl_image_new_wrapped(view.gst.context, image,
414             GST_GL_RGBA, &view, s_releaseImage);
415         GMutexHolder lock(view.images.mutex);
416
417         view.images.pending = gstImage;
418     },
419     // padding
420     nullptr, nullptr, nullptr, nullptr
421 };
422
423 void WPEThreadedView::s_releaseImage(GstEGLImage* image, gpointer data)
424 {
425     auto& view = *static_cast<WPEThreadedView*>(data);
426     GST_DEBUG("view %p image %" GST_PTR_FORMAT, &view, image);
427     view.releaseImage(gst_egl_image_get_image(image));
428 }