2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
4 * This file is part of CasparCG (www.casparcg.com).
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.
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.
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/>.
19 * Author: Robert Nagy, ronag89@gmail.com
24 #include "producer/html_producer.h"
25 #include "producer/html_cg_proxy.h"
27 #include <common/executor.h>
28 #include <common/future.h>
29 #include <common/env.h>
31 #include <core/producer/cg_proxy.h>
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>
47 #pragma warning(disable: 4458)
49 #include <cef_version.h>
52 #pragma comment(lib, "libcef.lib")
53 #pragma comment(lib, "libcef_dll_wrapper.lib")
55 namespace caspar { namespace html {
57 std::unique_ptr<executor> g_cef_executor;
60 const CefRefPtr<CefBrowser>& browser,
61 boost::log::trivial::severity_level level,
62 const std::string& message)
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);
73 class remove_handler : public CefV8Handler
75 CefRefPtr<CefBrowser> browser_;
77 remove_handler(CefRefPtr<CefBrowser> browser)
83 const CefString& name,
84 CefRefPtr<CefV8Value> object,
85 const CefV8ValueList& arguments,
86 CefRefPtr<CefV8Value>& retval,
87 CefString& exception) override
89 if (!CefCurrentlyOn(TID_RENDERER))
92 browser_->SendProcessMessage(
94 CefProcessMessage::Create(REMOVE_MESSAGE_NAME));
99 IMPLEMENT_REFCOUNTING(remove_handler);
102 class renderer_application : public CefApp, CefRenderProcessHandler
104 std::vector<CefRefPtr<CefV8Context>> contexts_;
106 CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override
111 void OnContextCreated(
112 CefRefPtr<CefBrowser> browser,
113 CefRefPtr<CefFrame> frame,
114 CefRefPtr<CefV8Context> context) override
116 caspar_log(browser, boost::log::trivial::trace,
118 + boost::lexical_cast<std::string>(frame->GetIdentifier())
120 contexts_.push_back(context);
122 auto window = context->GetGlobal();
126 CefV8Value::CreateFunction(
128 new remove_handler(browser)),
129 V8_PROPERTY_ATTRIBUTE_NONE);
131 CefRefPtr<CefV8Value> ret;
132 CefRefPtr<CefV8Exception> exception;
133 bool injected = context->Eval(R"(
134 var requestedAnimationFrames = {};
135 var currentAnimationFrameId = 0;
137 window.requestAnimationFrame = function(callback) {
138 requestedAnimationFrames[++currentAnimationFrameId] = callback;
139 return currentAnimationFrameId;
142 window.cancelAnimationFrame = function(animationFrameId) {
143 delete requestedAnimationFrames[animationFrameId];
146 function tickAnimations() {
147 var requestedFrames = requestedAnimationFrames;
148 var timestamp = performance.now();
149 requestedAnimationFrames = {};
151 for (var animationFrameId in requestedFrames)
152 if (requestedFrames.hasOwnProperty(animationFrameId))
153 requestedFrames[animationFrameId](timestamp);
158 caspar_log(browser, boost::log::trivial::error, "Could not inject javascript animation code.");
161 void OnContextReleased(
162 CefRefPtr<CefBrowser> browser,
163 CefRefPtr<CefFrame> frame,
164 CefRefPtr<CefV8Context> context)
166 auto removed = boost::remove_if(
167 contexts_, [&](const CefRefPtr<CefV8Context>& c)
169 return c->IsSame(context);
172 if (removed != contexts_.end())
173 caspar_log(browser, boost::log::trivial::trace,
175 + boost::lexical_cast<std::string>(frame->GetIdentifier())
178 caspar_log(browser, boost::log::trivial::warning,
180 + boost::lexical_cast<std::string>(frame->GetIdentifier())
181 + " released, but not found");
184 void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override
189 bool OnProcessMessageReceived(
190 CefRefPtr<CefBrowser> browser,
191 CefProcessId source_process,
192 CefRefPtr<CefProcessMessage> message) override
194 if (message->GetName().ToString() == TICK_MESSAGE_NAME)
196 for (auto& context : contexts_)
198 CefRefPtr<CefV8Value> ret;
199 CefRefPtr<CefV8Exception> exception;
200 context->Eval("tickAnimations()", ret, exception);
211 IMPLEMENT_REFCOUNTING(renderer_application);
214 bool intercept_command_line(int argc, char** argv)
217 CefMainArgs main_args;
219 CefMainArgs main_args(argc, argv);
222 if (CefExecuteProcess(main_args, CefRefPtr<CefApp>(new renderer_application), nullptr) >= 0)
228 void init(core::module_dependencies dependencies)
230 dependencies.producer_registry->register_producer_factory(L"HTML Producer", html::create_producer, html::describe_producer);
232 CefMainArgs main_args;
233 g_cef_executor.reset(new executor(L"cef"));
234 g_cef_executor->invoke([&]
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);
242 g_cef_executor->begin_invoke([&]
246 dependencies.cg_registry->register_cg_producer(
249 [](const std::wstring& filename)
253 [](const spl::shared_ptr<core::frame_producer>& producer)
255 return spl::make_shared<html_cg_proxy>(producer);
257 [](const core::frame_producer_dependencies& dependencies, const std::wstring& filename)
259 return html::create_producer(dependencies, { filename });
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));
271 dependencies.system_info_provider_repo->register_version_provider(L"cef", [=]
273 return cef_version_major + L"." + chrome_build + L"." + cef_revision;
275 dependencies.system_info_provider_repo->register_system_info_provider([=](boost::property_tree::wptree& info)
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);
286 CefQuitMessageLoop();
288 g_cef_executor->begin_invoke([&]
292 g_cef_executor.reset();
295 class cef_task : public CefTask
298 std::promise<void> promise_;
299 std::function<void ()> function_;
301 cef_task(const std::function<void ()>& function)
302 : function_(function)
306 void Execute() override
308 CASPAR_LOG_CALL(trace) << "[cef_task] executing task";
313 promise_.set_value();
314 CASPAR_LOG_CALL(trace) << "[cef_task] task succeeded";
318 promise_.set_exception(std::current_exception());
319 CASPAR_LOG(warning) << "[cef_task] task failed";
323 std::future<void> future()
325 return promise_.get_future();
328 IMPLEMENT_REFCOUNTING(cef_task);
331 void invoke(const std::function<void()>& func)
333 begin_invoke(func).get();
336 std::future<void> begin_invoke(const std::function<void()>& func)
338 CefRefPtr<cef_task> task = new cef_task(func);
340 if (CefCurrentlyOn(TID_UI))
344 return task->future();
347 if (CefPostTask(TID_UI, task.get()))
348 return task->future();
350 CASPAR_THROW_EXCEPTION(caspar_exception()
351 << msg_info("[cef_executor] Could not post task"));