]> git.sesse.net Git - casparcg/blob - modules/html/html.cpp
* Merged html producer and updated to latest CEF version (does not have satisfactory...
[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                 BOOST_FOREACH(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                 BOOST_FOREACH(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                         BOOST_FOREACH(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         CefMainArgs main_args;
276
277         if (CefExecuteProcess(main_args, CefRefPtr<CefApp>(new renderer_application), nullptr) >= 0)
278                 return true;
279
280         return false;
281 }
282
283 void init(core::module_dependencies dependencies)
284 {
285         core::register_producer_factory(html::create_producer);
286         
287         CefMainArgs main_args;
288         g_cef_executor.reset(new executor(L"cef"));
289         g_cef_executor->invoke([&]
290         {
291                 CefSettings settings;
292                 settings.windowless_rendering_enabled = true;
293                 CefInitialize(main_args, settings, nullptr, nullptr);
294         });
295         g_cef_executor->begin_invoke([&]
296         {
297                 CefRunMessageLoop();
298         });
299         dependencies.cg_registry->register_cg_producer(
300                         L"html",
301                         { L".html" },
302                         [](const std::wstring& filename)
303                         {
304                                 return "";
305                         },
306                         [](const spl::shared_ptr<core::frame_producer>& producer)
307                         {
308                                 return spl::make_shared<html_cg_proxy>(producer);
309                         },
310                         [](const spl::shared_ptr<core::frame_factory>& ff, const core::video_format_desc& f, const std::wstring& filename)
311                         {
312                                 return html::create_producer(ff, f, { filename });
313                         },
314                         false
315         );
316 }
317
318 void uninit()
319 {
320         invoke([]
321         {
322                 CefQuitMessageLoop();
323         });
324         g_cef_executor->begin_invoke([&]
325         {
326                 CefShutdown();
327         });
328 }
329
330 class cef_task : public CefTask
331 {
332 private:
333         std::promise<void> promise_;
334         std::function<void ()> function_;
335 public:
336         cef_task(const std::function<void ()>& function)
337                 : function_(function)
338         {
339         }
340
341         void Execute() override
342         {
343                 CASPAR_LOG(trace) << "[cef_task] executing task";
344
345                 try
346                 {
347                         function_();
348                         promise_.set_value();
349                         CASPAR_LOG(trace) << "[cef_task] task succeeded";
350                 }
351                 catch (...)
352                 {
353                         promise_.set_exception(std::current_exception());
354                         CASPAR_LOG(warning) << "[cef_task] task failed";
355                 }
356         }
357
358         std::future<void> future()
359         {
360                 return promise_.get_future();
361         }
362
363         IMPLEMENT_REFCOUNTING(cef_task);
364 };
365
366 void invoke(const std::function<void()>& func)
367 {
368         begin_invoke(func).get();
369 }
370
371 std::future<void> begin_invoke(const std::function<void()>& func)
372 {
373         CefRefPtr<cef_task> task = new cef_task(func);
374
375         if (CefCurrentlyOn(TID_UI))
376         {
377                 // Avoid deadlock.
378                 task->Execute();
379                 return task->future();
380         }
381
382         if (CefPostTask(TID_UI, task.get()))
383                 return task->future();
384         else
385                 BOOST_THROW_EXCEPTION(caspar_exception()
386                                 << msg_info("[cef_executor] Could not post task"));
387 }
388
389 }}