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