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