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