]> git.sesse.net Git - casparcg/blob - modules/html/html.cpp
Merge pull request #374 from hummelstrand/readme-2.1.0-update
[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 #pragma warning(push)
43 #pragma warning(disable: 4458)
44 #include <cef_app.h>
45 #pragma warning(pop)
46
47 #pragma comment(lib, "libcef.lib")
48 #pragma comment(lib, "libcef_dll_wrapper.lib")
49
50 namespace caspar { namespace html {
51
52 std::unique_ptr<executor> g_cef_executor;
53
54 void caspar_log(
55                 const CefRefPtr<CefBrowser>& browser,
56                 boost::log::trivial::severity_level level,
57                 const std::string& message)
58 {
59         if (browser)
60         {
61                 auto msg = CefProcessMessage::Create(LOG_MESSAGE_NAME);
62                 msg->GetArgumentList()->SetInt(0, level);
63                 msg->GetArgumentList()->SetString(1, message);
64                 browser->SendProcessMessage(PID_BROWSER, msg);
65         }
66 }
67
68 class animation_handler : public CefV8Handler
69 {
70 private:
71         std::vector<CefRefPtr<CefV8Value>>                      callbacks_;
72         boost::timer                                                            since_start_timer_;
73 public:
74         CefRefPtr<CefBrowser>                                           browser;
75         std::function<CefRefPtr<CefV8Context>()>        get_context;
76
77         bool Execute(
78                         const CefString& name,
79                         CefRefPtr<CefV8Value> object,
80                         const CefV8ValueList& arguments,
81                         CefRefPtr<CefV8Value>& retval,
82                         CefString& exception) override
83         {
84                 if (!CefCurrentlyOn(TID_RENDERER))
85                         return false;
86
87                 if (arguments.size() < 1 || !arguments.at(0)->IsFunction())
88                 {
89                         return false;
90                 }
91
92                 callbacks_.push_back(arguments.at(0));
93
94                 if (browser)
95                         browser->SendProcessMessage(PID_BROWSER, CefProcessMessage::Create(
96                                         ANIMATION_FRAME_REQUESTED_MESSAGE_NAME));
97
98                 return true;
99         }
100
101         void tick()
102         {
103                 if (!get_context)
104                         return;
105
106                 auto context = get_context();
107
108                 if (!context)
109                         return;
110
111                 if (!CefCurrentlyOn(TID_RENDERER))
112                         return;
113
114                 std::vector<CefRefPtr<CefV8Value>> callbacks;
115                 callbacks_.swap(callbacks);
116
117                 CefV8ValueList callback_args;
118                 CefTime timestamp;
119                 timestamp.Now();
120                 callback_args.push_back(CefV8Value::CreateDouble(
121                                 since_start_timer_.elapsed() * 1000.0));
122
123                 for (auto callback : callbacks)
124                 {
125                         callback->ExecuteFunctionWithContext(
126                                         context, callback, callback_args);
127                 }
128         }
129
130         IMPLEMENT_REFCOUNTING(animation_handler);
131 };
132
133 class remove_handler : public CefV8Handler
134 {
135         CefRefPtr<CefBrowser> browser_;
136 public:
137         remove_handler(CefRefPtr<CefBrowser> browser)
138                 : browser_(browser)
139         {
140         }
141
142         bool Execute(
143                         const CefString& name,
144                         CefRefPtr<CefV8Value> object,
145                         const CefV8ValueList& arguments,
146                         CefRefPtr<CefV8Value>& retval,
147                         CefString& exception) override
148         {
149                 if (!CefCurrentlyOn(TID_RENDERER))
150                         return false;
151
152                 browser_->SendProcessMessage(
153                                 PID_BROWSER,
154                                 CefProcessMessage::Create(REMOVE_MESSAGE_NAME));
155
156                 return true;
157         }
158
159         IMPLEMENT_REFCOUNTING(remove_handler);
160 };
161
162 class renderer_application : public CefApp, CefRenderProcessHandler
163 {
164         std::vector<std::pair<CefRefPtr<animation_handler>, CefRefPtr<CefV8Context>>> contexts_per_handlers_;
165         //std::map<CefRefPtr<animation_handler>, CefRefPtr<CefV8Context>> contexts_per_handlers_;
166 public:
167         CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override
168         {
169                 return this;
170         }
171
172         CefRefPtr<CefV8Context> get_context(
173                         const CefRefPtr<animation_handler>& handler)
174         {
175                 for (auto& ctx : contexts_per_handlers_)
176                 {
177                         if (ctx.first == handler)
178                                 return ctx.second;
179                 }
180
181                 return nullptr;
182         }
183
184         void OnContextCreated(
185                         CefRefPtr<CefBrowser> browser,
186                         CefRefPtr<CefFrame> frame,
187                         CefRefPtr<CefV8Context> context) override
188         {
189                 caspar_log(browser, boost::log::trivial::trace,
190                                 "context for frame "
191                                 + boost::lexical_cast<std::string>(frame->GetIdentifier())
192                                 + " created");
193
194                 CefRefPtr<animation_handler> handler = new animation_handler;
195                 contexts_per_handlers_.push_back(std::make_pair(handler, context));
196                 auto handler_ptr = handler.get();
197
198                 handler->browser = browser;
199                 handler->get_context = [this, handler_ptr]
200                 {
201                         return get_context(handler_ptr);
202                 };
203
204                 auto window = context->GetGlobal();
205
206                 auto function = CefV8Value::CreateFunction(
207                                 "requestAnimationFrame",
208                                 handler.get());
209                 window->SetValue(
210                                 "requestAnimationFrame",
211                                 function,
212                                 V8_PROPERTY_ATTRIBUTE_NONE);
213
214                 function = CefV8Value::CreateFunction(
215                                 "remove",
216                                 new remove_handler(browser));
217                 window->SetValue(
218                                 "remove",
219                                 function,
220                                 V8_PROPERTY_ATTRIBUTE_NONE);
221         }
222
223         void OnContextReleased(
224                         CefRefPtr<CefBrowser> browser,
225                         CefRefPtr<CefFrame> frame,
226                         CefRefPtr<CefV8Context> context)
227         {
228                 auto removed = boost::remove_if(
229                                 contexts_per_handlers_, [&](const std::pair<
230                                                 CefRefPtr<animation_handler>,
231                                                 CefRefPtr<CefV8Context>>& c)
232                 {
233                         return c.second->IsSame(context);
234                 });
235
236                 if (removed != contexts_per_handlers_.end())
237                         caspar_log(browser, boost::log::trivial::trace,
238                                         "context for frame "
239                                         + boost::lexical_cast<std::string>(frame->GetIdentifier())
240                                         + " released");
241                 else
242                         caspar_log(browser, boost::log::trivial::warning,
243                                         "context for frame "
244                                         + boost::lexical_cast<std::string>(frame->GetIdentifier())
245                                         + " released, but not found");
246         }
247
248         void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override
249         {
250                 contexts_per_handlers_.clear();
251         }
252
253         bool OnProcessMessageReceived(
254                         CefRefPtr<CefBrowser> browser,
255                         CefProcessId source_process,
256                         CefRefPtr<CefProcessMessage> message) override
257         {
258                 if (message->GetName().ToString() == TICK_MESSAGE_NAME)
259                 {
260                         for (auto& handler : contexts_per_handlers_)
261                         {
262                                 handler.first->tick();
263                         }
264
265                         return true;
266                 }
267                 else
268                 {
269                         return false;
270                 }
271         }
272
273         IMPLEMENT_REFCOUNTING(renderer_application);
274 };
275
276 bool intercept_command_line(int argc, char** argv)
277 {
278 #ifdef _WIN32
279         CefMainArgs main_args;
280 #else
281         CefMainArgs main_args(argc, argv);
282 #endif
283
284         if (CefExecuteProcess(main_args, CefRefPtr<CefApp>(new renderer_application), nullptr) >= 0)
285                 return true;
286
287         return false;
288 }
289
290 void init(core::module_dependencies dependencies)
291 {
292         dependencies.producer_registry->register_producer_factory(L"HTML Producer", html::create_producer, html::describe_producer);
293         
294         CefMainArgs main_args;
295         g_cef_executor.reset(new executor(L"cef"));
296         g_cef_executor->invoke([&]
297         {
298                 CefSettings settings;
299                 settings.no_sandbox = true;
300                 //settings.windowless_rendering_enabled = true;
301                 CefInitialize(main_args, settings, nullptr, nullptr);
302         });
303         g_cef_executor->begin_invoke([&]
304         {
305                 CefRunMessageLoop();
306         });
307         dependencies.cg_registry->register_cg_producer(
308                         L"html",
309                         { L".html" },
310                         [](const std::wstring& filename)
311                         {
312                                 return "";
313                         },
314                         [](const spl::shared_ptr<core::frame_producer>& producer)
315                         {
316                                 return spl::make_shared<html_cg_proxy>(producer);
317                         },
318                         [](const core::frame_producer_dependencies& dependencies, const std::wstring& filename)
319                         {
320                                 return html::create_producer(dependencies, { filename });
321                         },
322                         false
323         );
324 }
325
326 void uninit()
327 {
328         invoke([]
329         {
330                 CefQuitMessageLoop();
331         });
332         g_cef_executor->begin_invoke([&]
333         {
334                 CefShutdown();
335         });
336         g_cef_executor.reset();
337 }
338
339 class cef_task : public CefTask
340 {
341 private:
342         std::promise<void> promise_;
343         std::function<void ()> function_;
344 public:
345         cef_task(const std::function<void ()>& function)
346                 : function_(function)
347         {
348         }
349
350         void Execute() override
351         {
352                 CASPAR_LOG(trace) << "[cef_task] executing task";
353
354                 try
355                 {
356                         function_();
357                         promise_.set_value();
358                         CASPAR_LOG(trace) << "[cef_task] task succeeded";
359                 }
360                 catch (...)
361                 {
362                         promise_.set_exception(std::current_exception());
363                         CASPAR_LOG(warning) << "[cef_task] task failed";
364                 }
365         }
366
367         std::future<void> future()
368         {
369                 return promise_.get_future();
370         }
371
372         IMPLEMENT_REFCOUNTING(cef_task);
373 };
374
375 void invoke(const std::function<void()>& func)
376 {
377         begin_invoke(func).get();
378 }
379
380 std::future<void> begin_invoke(const std::function<void()>& func)
381 {
382         CefRefPtr<cef_task> task = new cef_task(func);
383
384         if (CefCurrentlyOn(TID_UI))
385         {
386                 // Avoid deadlock.
387                 task->Execute();
388                 return task->future();
389         }
390
391         if (CefPostTask(TID_UI, task.get()))
392                 return task->future();
393         else
394                 CASPAR_THROW_EXCEPTION(caspar_exception()
395                                 << msg_info("[cef_executor] Could not post task"));
396 }
397
398 }}