]> git.sesse.net Git - casparcg/blob - modules/html/html.cpp
- Removed need of non-deterministic sleeps during server shutdown.
[casparcg] / modules / html / html.cpp
1 /*
2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "html.h"
23
24 #include "producer/html_producer.h"
25 #include "producer/html_cg_proxy.h"
26
27 #include <common/executor.h>
28 #include <common/future.h>
29
30 #include <core/producer/cg_proxy.h>
31
32 #include <boost/thread.hpp>
33 #include <boost/asio.hpp>
34 #include <boost/date_time/posix_time/posix_time.hpp>
35 #include <boost/foreach.hpp>
36 #include <boost/timer.hpp>
37 #include <boost/range/algorithm/remove_if.hpp>
38 #include <boost/thread/future.hpp>
39 #include <boost/lexical_cast.hpp>
40 #include <boost/log/trivial.hpp>
41
42 #include <cef_app.h>
43
44 #pragma comment(lib, "libcef.lib")
45 #pragma comment(lib, "libcef_dll_wrapper.lib")
46
47 namespace caspar { namespace html {
48
49 std::unique_ptr<executor> g_cef_executor;
50
51 void caspar_log(
52                 const CefRefPtr<CefBrowser>& browser,
53                 boost::log::trivial::severity_level level,
54                 const std::string& message)
55 {
56         if (browser)
57         {
58                 auto msg = CefProcessMessage::Create(LOG_MESSAGE_NAME);
59                 msg->GetArgumentList()->SetInt(0, level);
60                 msg->GetArgumentList()->SetString(1, message);
61                 browser->SendProcessMessage(PID_BROWSER, msg);
62         }
63 }
64
65 class animation_handler : public CefV8Handler
66 {
67 private:
68         std::vector<CefRefPtr<CefV8Value>>                      callbacks_;
69         boost::timer                                                            since_start_timer_;
70 public:
71         CefRefPtr<CefBrowser>                                           browser;
72         std::function<CefRefPtr<CefV8Context>()>        get_context;
73
74         bool Execute(
75                         const CefString& name,
76                         CefRefPtr<CefV8Value> object,
77                         const CefV8ValueList& arguments,
78                         CefRefPtr<CefV8Value>& retval,
79                         CefString& exception) override
80         {
81                 if (!CefCurrentlyOn(TID_RENDERER))
82                         return false;
83
84                 if (arguments.size() < 1 || !arguments.at(0)->IsFunction())
85                 {
86                         return false;
87                 }
88
89                 callbacks_.push_back(arguments.at(0));
90
91                 if (browser)
92                         browser->SendProcessMessage(PID_BROWSER, CefProcessMessage::Create(
93                                         ANIMATION_FRAME_REQUESTED_MESSAGE_NAME));
94
95                 return true;
96         }
97
98         void tick()
99         {
100                 if (!get_context)
101                         return;
102
103                 auto context = get_context();
104
105                 if (!context)
106                         return;
107
108                 if (!CefCurrentlyOn(TID_RENDERER))
109                         return;
110
111                 std::vector<CefRefPtr<CefV8Value>> callbacks;
112                 callbacks_.swap(callbacks);
113
114                 CefV8ValueList callback_args;
115                 CefTime timestamp;
116                 timestamp.Now();
117                 callback_args.push_back(CefV8Value::CreateDouble(
118                                 since_start_timer_.elapsed() * 1000.0));
119
120                 for (auto callback : callbacks)
121                 {
122                         callback->ExecuteFunctionWithContext(
123                                         context, callback, callback_args);
124                 }
125         }
126
127         IMPLEMENT_REFCOUNTING(animation_handler);
128 };
129
130 class remove_handler : public CefV8Handler
131 {
132         CefRefPtr<CefBrowser> browser_;
133 public:
134         remove_handler(CefRefPtr<CefBrowser> browser)
135                 : browser_(browser)
136         {
137         }
138
139         bool Execute(
140                         const CefString& name,
141                         CefRefPtr<CefV8Value> object,
142                         const CefV8ValueList& arguments,
143                         CefRefPtr<CefV8Value>& retval,
144                         CefString& exception) override
145         {
146                 if (!CefCurrentlyOn(TID_RENDERER))
147                         return false;
148
149                 browser_->SendProcessMessage(
150                                 PID_BROWSER,
151                                 CefProcessMessage::Create(REMOVE_MESSAGE_NAME));
152
153                 return true;
154         }
155
156         IMPLEMENT_REFCOUNTING(remove_handler);
157 };
158
159 class renderer_application : public CefApp, CefRenderProcessHandler
160 {
161         std::vector<std::pair<CefRefPtr<animation_handler>, CefRefPtr<CefV8Context>>> contexts_per_handlers_;
162         //std::map<CefRefPtr<animation_handler>, CefRefPtr<CefV8Context>> contexts_per_handlers_;
163 public:
164         CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override
165         {
166                 return this;
167         }
168
169         CefRefPtr<CefV8Context> get_context(
170                         const CefRefPtr<animation_handler>& handler)
171         {
172                 for (auto& ctx : contexts_per_handlers_)
173                 {
174                         if (ctx.first == handler)
175                                 return ctx.second;
176                 }
177
178                 return nullptr;
179         }
180
181         void OnContextCreated(
182                         CefRefPtr<CefBrowser> browser,
183                         CefRefPtr<CefFrame> frame,
184                         CefRefPtr<CefV8Context> context) override
185         {
186                 caspar_log(browser, boost::log::trivial::trace,
187                                 "context for frame "
188                                 + boost::lexical_cast<std::string>(frame->GetIdentifier())
189                                 + " created");
190
191                 CefRefPtr<animation_handler> handler = new animation_handler;
192                 contexts_per_handlers_.push_back(std::make_pair(handler, context));
193                 auto handler_ptr = handler.get();
194
195                 handler->browser = browser;
196                 handler->get_context = [this, handler_ptr]
197                 {
198                         return get_context(handler_ptr);
199                 };
200
201                 auto window = context->GetGlobal();
202
203                 auto function = CefV8Value::CreateFunction(
204                                 "requestAnimationFrame",
205                                 handler.get());
206                 window->SetValue(
207                                 "requestAnimationFrame",
208                                 function,
209                                 V8_PROPERTY_ATTRIBUTE_NONE);
210
211                 function = CefV8Value::CreateFunction(
212                                 "remove",
213                                 new remove_handler(browser));
214                 window->SetValue(
215                                 "remove",
216                                 function,
217                                 V8_PROPERTY_ATTRIBUTE_NONE);
218         }
219
220         void OnContextReleased(
221                         CefRefPtr<CefBrowser> browser,
222                         CefRefPtr<CefFrame> frame,
223                         CefRefPtr<CefV8Context> context)
224         {
225                 auto removed = boost::remove_if(
226                                 contexts_per_handlers_, [&](const std::pair<
227                                                 CefRefPtr<animation_handler>,
228                                                 CefRefPtr<CefV8Context>>& c)
229                 {
230                         return c.second->IsSame(context);
231                 });
232
233                 if (removed != contexts_per_handlers_.end())
234                         caspar_log(browser, boost::log::trivial::trace,
235                                         "context for frame "
236                                         + boost::lexical_cast<std::string>(frame->GetIdentifier())
237                                         + " released");
238                 else
239                         caspar_log(browser, boost::log::trivial::warning,
240                                         "context for frame "
241                                         + boost::lexical_cast<std::string>(frame->GetIdentifier())
242                                         + " released, but not found");
243         }
244
245         void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override
246         {
247                 contexts_per_handlers_.clear();
248         }
249
250         bool OnProcessMessageReceived(
251                         CefRefPtr<CefBrowser> browser,
252                         CefProcessId source_process,
253                         CefRefPtr<CefProcessMessage> message) override
254         {
255                 if (message->GetName().ToString() == TICK_MESSAGE_NAME)
256                 {
257                         for (auto& handler : contexts_per_handlers_)
258                         {
259                                 handler.first->tick();
260                         }
261
262                         return true;
263                 }
264                 else
265                 {
266                         return false;
267                 }
268         }
269
270         IMPLEMENT_REFCOUNTING(renderer_application);
271 };
272
273 bool intercept_command_line(int argc, char** argv)
274 {
275 #ifdef _WIN32
276         CefMainArgs main_args;
277 #else
278         CefMainArgs main_args(argc, argv);
279 #endif
280
281         if (CefExecuteProcess(main_args, CefRefPtr<CefApp>(new renderer_application), nullptr) >= 0)
282                 return true;
283
284         return false;
285 }
286
287 void init(core::module_dependencies dependencies)
288 {
289         dependencies.producer_registry->register_producer_factory(L"HTML Producer", html::create_producer, html::describe_producer);
290         
291         CefMainArgs main_args;
292         g_cef_executor.reset(new executor(L"cef"));
293         g_cef_executor->invoke([&]
294         {
295                 CefSettings settings;
296                 settings.no_sandbox = true;
297                 //settings.windowless_rendering_enabled = true;
298                 CefInitialize(main_args, settings, nullptr, nullptr);
299         });
300         g_cef_executor->begin_invoke([&]
301         {
302                 CefRunMessageLoop();
303         });
304         dependencies.cg_registry->register_cg_producer(
305                         L"html",
306                         { L".html" },
307                         [](const std::wstring& filename)
308                         {
309                                 return "";
310                         },
311                         [](const spl::shared_ptr<core::frame_producer>& producer)
312                         {
313                                 return spl::make_shared<html_cg_proxy>(producer);
314                         },
315                         [](const core::frame_producer_dependencies& dependencies, const std::wstring& filename)
316                         {
317                                 return html::create_producer(dependencies, { filename });
318                         },
319                         false
320         );
321 }
322
323 void uninit()
324 {
325         invoke([]
326         {
327                 CefQuitMessageLoop();
328         });
329         g_cef_executor->begin_invoke([&]
330         {
331                 CefShutdown();
332         });
333         g_cef_executor.reset();
334 }
335
336 class cef_task : public CefTask
337 {
338 private:
339         std::promise<void> promise_;
340         std::function<void ()> function_;
341 public:
342         cef_task(const std::function<void ()>& function)
343                 : function_(function)
344         {
345         }
346
347         void Execute() override
348         {
349                 CASPAR_LOG(trace) << "[cef_task] executing task";
350
351                 try
352                 {
353                         function_();
354                         promise_.set_value();
355                         CASPAR_LOG(trace) << "[cef_task] task succeeded";
356                 }
357                 catch (...)
358                 {
359                         promise_.set_exception(std::current_exception());
360                         CASPAR_LOG(warning) << "[cef_task] task failed";
361                 }
362         }
363
364         std::future<void> future()
365         {
366                 return promise_.get_future();
367         }
368
369         IMPLEMENT_REFCOUNTING(cef_task);
370 };
371
372 void invoke(const std::function<void()>& func)
373 {
374         begin_invoke(func).get();
375 }
376
377 std::future<void> begin_invoke(const std::function<void()>& func)
378 {
379         CefRefPtr<cef_task> task = new cef_task(func);
380
381         if (CefCurrentlyOn(TID_UI))
382         {
383                 // Avoid deadlock.
384                 task->Execute();
385                 return task->future();
386         }
387
388         if (CefPostTask(TID_UI, task.get()))
389                 return task->future();
390         else
391                 BOOST_THROW_EXCEPTION(caspar_exception()
392                                 << msg_info("[cef_executor] Could not post task"));
393 }
394
395 }}