1 // Copyright (c) 2012 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4
5 #include "libcef/browser/context.h"
6
7 #include "libcef/browser/browser_info_manager.h"
8 #include "libcef/browser/request_context_impl.h"
9 #include "libcef/browser/thread_util.h"
10 #include "libcef/browser/trace_subscriber.h"
11 #include "libcef/common/cef_switches.h"
12
13 #include "base/bind.h"
14 #include "base/files/file_util.h"
15 #include "base/run_loop.h"
16 #include "base/task/current_thread.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "components/network_session_configurator/common/network_switches.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/notification_types.h"
21 #include "ui/base/ui_base_switches.h"
22
23 #if BUILDFLAG(IS_WIN)
24 #include "base/strings/utf_string_conversions.h"
25 #include "chrome/chrome_elf/chrome_elf_main.h"
26 #include "chrome/install_static/initialize_from_primary_module.h"
27 #endif
28
29 namespace {
30
31 CefContext* g_context = nullptr;
32
33 #if DCHECK_IS_ON()
34 // When the process terminates check if CefShutdown() has been called.
35 class CefShutdownChecker {
36 public:
~CefShutdownChecker()37 ~CefShutdownChecker() { DCHECK(!g_context) << "CefShutdown was not called"; }
38 } g_shutdown_checker;
39 #endif // DCHECK_IS_ON()
40
41 #if BUILDFLAG(IS_WIN)
42 #if defined(ARCH_CPU_X86_64)
43 // VS2013 only checks the existence of FMA3 instructions, not the enabled-ness
44 // of them at the OS level (this is fixed in VS2015). We force off usage of
45 // FMA3 instructions in the CRT to avoid using that path and hitting illegal
46 // instructions when running on CPUs that support FMA3, but OSs that don't.
DisableFMA3()47 void DisableFMA3() {
48 static bool disabled = false;
49 if (disabled)
50 return;
51 disabled = true;
52 _set_FMA3_enable(0);
53 }
54 #endif // defined(ARCH_CPU_X86_64)
55
56 // Transfer state from chrome_elf.dll to the libcef.dll. Accessed when
57 // loading chrome://system.
InitInstallDetails()58 void InitInstallDetails() {
59 static bool initialized = false;
60 if (initialized)
61 return;
62 initialized = true;
63 install_static::InitializeFromPrimaryModule();
64 }
65
66 // Signal chrome_elf to initialize crash reporting, rather than doing it in
67 // DllMain. See https://crbug.com/656800 for details.
InitCrashReporter()68 void InitCrashReporter() {
69 static bool initialized = false;
70 if (initialized)
71 return;
72 initialized = true;
73 SignalInitializeCrashReporting();
74 }
75 #endif // BUILDFLAG(IS_WIN)
76
GetColor(const cef_color_t cef_in,bool is_windowless,SkColor * sk_out)77 bool GetColor(const cef_color_t cef_in, bool is_windowless, SkColor* sk_out) {
78 // Windowed browser colors must be fully opaque.
79 if (!is_windowless && CefColorGetA(cef_in) != SK_AlphaOPAQUE)
80 return false;
81
82 // Windowless browser colors may be fully transparent.
83 if (is_windowless && CefColorGetA(cef_in) == SK_AlphaTRANSPARENT) {
84 *sk_out = SK_ColorTRANSPARENT;
85 return true;
86 }
87
88 // Ignore the alpha component.
89 *sk_out = SkColorSetRGB(CefColorGetR(cef_in), CefColorGetG(cef_in),
90 CefColorGetB(cef_in));
91 return true;
92 }
93
94 // Convert |path_str| to a normalized FilePath.
NormalizePath(const cef_string_t & path_str,const char * name,bool * has_error=nullptr)95 base::FilePath NormalizePath(const cef_string_t& path_str,
96 const char* name,
97 bool* has_error = nullptr) {
98 if (has_error)
99 *has_error = false;
100
101 base::FilePath path = base::FilePath(CefString(&path_str));
102 if (path.EndsWithSeparator()) {
103 // Remove the trailing separator because it will interfere with future
104 // equality checks.
105 path = path.StripTrailingSeparators();
106 }
107
108 if (!path.empty() && !path.IsAbsolute()) {
109 LOG(ERROR) << "The " << name << " directory (" << path.value()
110 << ") is not an absolute path. Defaulting to empty.";
111 if (has_error)
112 *has_error = true;
113 path = base::FilePath();
114 }
115
116 return path;
117 }
118
SetPath(cef_string_t & path_str,const base::FilePath & path)119 void SetPath(cef_string_t& path_str, const base::FilePath& path) {
120 #if BUILDFLAG(IS_WIN)
121 CefString(&path_str).FromWString(path.value());
122 #else
123 CefString(&path_str).FromString(path.value());
124 #endif
125 }
126
127 // Convert |path_str| to a normalized FilePath and update the |path_str| value.
NormalizePathAndSet(cef_string_t & path_str,const char * name)128 base::FilePath NormalizePathAndSet(cef_string_t& path_str, const char* name) {
129 const base::FilePath& path = NormalizePath(path_str, name);
130 SetPath(path_str, path);
131 return path;
132 }
133
134 // Verify that |cache_path| is valid and create it if necessary.
ValidateCachePath(const base::FilePath & cache_path,const base::FilePath & root_cache_path)135 bool ValidateCachePath(const base::FilePath& cache_path,
136 const base::FilePath& root_cache_path) {
137 if (cache_path.empty())
138 return true;
139
140 if (!root_cache_path.empty() && root_cache_path != cache_path &&
141 !root_cache_path.IsParent(cache_path)) {
142 LOG(ERROR) << "The cache_path directory (" << cache_path.value()
143 << ") is not a child of the root_cache_path directory ("
144 << root_cache_path.value() << ")";
145 return false;
146 }
147
148 base::ThreadRestrictions::ScopedAllowIO allow_io;
149 if (!base::DirectoryExists(cache_path) &&
150 !base::CreateDirectory(cache_path)) {
151 LOG(ERROR) << "The cache_path directory (" << cache_path.value()
152 << ") could not be created.";
153 return false;
154 }
155
156 return true;
157 }
158
159 // Like NormalizePathAndSet but with additional checks specific to the
160 // cache_path value.
NormalizeCachePathAndSet(cef_string_t & path_str,const base::FilePath & root_cache_path)161 base::FilePath NormalizeCachePathAndSet(cef_string_t& path_str,
162 const base::FilePath& root_cache_path) {
163 bool has_error = false;
164 base::FilePath path = NormalizePath(path_str, "cache_path", &has_error);
165 if (has_error || !ValidateCachePath(path, root_cache_path)) {
166 LOG(ERROR) << "The cache_path is invalid. Defaulting to in-memory storage.";
167 path = base::FilePath();
168 }
169 SetPath(path_str, path);
170 return path;
171 }
172
173 } // namespace
174
CefExecuteProcess(const CefMainArgs & args,CefRefPtr<CefApp> application,void * windows_sandbox_info)175 int CefExecuteProcess(const CefMainArgs& args,
176 CefRefPtr<CefApp> application,
177 void* windows_sandbox_info) {
178 #if BUILDFLAG(IS_WIN)
179 #if defined(ARCH_CPU_X86_64)
180 DisableFMA3();
181 #endif
182 InitInstallDetails();
183 InitCrashReporter();
184 #endif
185
186 return CefMainRunner::RunAsHelperProcess(args, application,
187 windows_sandbox_info);
188 }
189
CefInitialize(const CefMainArgs & args,const CefSettings & settings,CefRefPtr<CefApp> application,void * windows_sandbox_info)190 bool CefInitialize(const CefMainArgs& args,
191 const CefSettings& settings,
192 CefRefPtr<CefApp> application,
193 void* windows_sandbox_info) {
194 #if BUILDFLAG(IS_WIN)
195 #if defined(ARCH_CPU_X86_64)
196 DisableFMA3();
197 #endif
198 InitInstallDetails();
199 InitCrashReporter();
200 #endif
201
202 // Return true if the global context already exists.
203 if (g_context)
204 return true;
205
206 if (settings.size != sizeof(cef_settings_t)) {
207 NOTREACHED() << "invalid CefSettings structure size";
208 return false;
209 }
210
211 // Create the new global context object.
212 g_context = new CefContext();
213
214 // Initialize the global context.
215 return g_context->Initialize(args, settings, application,
216 windows_sandbox_info);
217 }
218
CefShutdown()219 void CefShutdown() {
220 // Verify that the context is in a valid state.
221 if (!CONTEXT_STATE_VALID()) {
222 NOTREACHED() << "context not valid";
223 return;
224 }
225
226 // Must always be called on the same thread as Initialize.
227 if (!g_context->OnInitThread()) {
228 NOTREACHED() << "called on invalid thread";
229 return;
230 }
231
232 // Shut down the global context. This will block until shutdown is complete.
233 g_context->Shutdown();
234
235 // Delete the global context object.
236 delete g_context;
237 g_context = nullptr;
238 }
239
CefDoMessageLoopWork()240 void CefDoMessageLoopWork() {
241 // Verify that the context is in a valid state.
242 if (!CONTEXT_STATE_VALID()) {
243 NOTREACHED() << "context not valid";
244 return;
245 }
246
247 // Must always be called on the same thread as Initialize.
248 if (!g_context->OnInitThread()) {
249 NOTREACHED() << "called on invalid thread";
250 return;
251 }
252
253 base::RunLoop run_loop;
254 run_loop.RunUntilIdle();
255 }
256
CefRunMessageLoop()257 void CefRunMessageLoop() {
258 // Verify that the context is in a valid state.
259 if (!CONTEXT_STATE_VALID()) {
260 NOTREACHED() << "context not valid";
261 return;
262 }
263
264 // Must always be called on the same thread as Initialize.
265 if (!g_context->OnInitThread()) {
266 NOTREACHED() << "called on invalid thread";
267 return;
268 }
269
270 g_context->RunMessageLoop();
271 }
272
CefQuitMessageLoop()273 void CefQuitMessageLoop() {
274 // Verify that the context is in a valid state.
275 if (!CONTEXT_STATE_VALID()) {
276 NOTREACHED() << "context not valid";
277 return;
278 }
279
280 // Must always be called on the same thread as Initialize.
281 if (!g_context->OnInitThread()) {
282 NOTREACHED() << "called on invalid thread";
283 return;
284 }
285
286 g_context->QuitMessageLoop();
287 }
288
CefSetOSModalLoop(bool osModalLoop)289 void CefSetOSModalLoop(bool osModalLoop) {
290 #if BUILDFLAG(IS_WIN)
291 // Verify that the context is in a valid state.
292 if (!CONTEXT_STATE_VALID()) {
293 NOTREACHED() << "context not valid";
294 return;
295 }
296
297 if (!CEF_CURRENTLY_ON_UIT()) {
298 CEF_POST_TASK(CEF_UIT, base::BindOnce(CefSetOSModalLoop, osModalLoop));
299 return;
300 }
301
302 base::CurrentThread::Get()->set_os_modal_loop(osModalLoop);
303 #endif // BUILDFLAG(IS_WIN)
304 }
305
306 // CefContext
307
CefContext()308 CefContext::CefContext()
309 : initialized_(false), shutting_down_(false), init_thread_id_(0) {}
310
~CefContext()311 CefContext::~CefContext() {}
312
313 // static
Get()314 CefContext* CefContext::Get() {
315 return g_context;
316 }
317
Initialize(const CefMainArgs & args,const CefSettings & settings,CefRefPtr<CefApp> application,void * windows_sandbox_info)318 bool CefContext::Initialize(const CefMainArgs& args,
319 const CefSettings& settings,
320 CefRefPtr<CefApp> application,
321 void* windows_sandbox_info) {
322 init_thread_id_ = base::PlatformThread::CurrentId();
323 settings_ = settings;
324 application_ = application;
325
326 #if !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX))
327 if (settings.multi_threaded_message_loop) {
328 NOTIMPLEMENTED() << "multi_threaded_message_loop is not supported.";
329 return false;
330 }
331 #endif
332
333 #if BUILDFLAG(IS_WIN)
334 // Signal Chrome Elf that Chrome has begun to start.
335 SignalChromeElf();
336 #endif
337
338 const base::FilePath& root_cache_path =
339 NormalizePathAndSet(settings_.root_cache_path, "root_cache_path");
340 const base::FilePath& cache_path =
341 NormalizeCachePathAndSet(settings_.cache_path, root_cache_path);
342 if (root_cache_path.empty() && !cache_path.empty()) {
343 CefString(&settings_.root_cache_path) = cache_path.value();
344 }
345
346 // All other paths that need to be normalized.
347 NormalizePathAndSet(settings_.browser_subprocess_path,
348 "browser_subprocess_path");
349 NormalizePathAndSet(settings_.framework_dir_path, "framework_dir_path");
350 NormalizePathAndSet(settings_.main_bundle_path, "main_bundle_path");
351 NormalizePathAndSet(settings_.user_data_path, "user_data_path");
352 NormalizePathAndSet(settings_.resources_dir_path, "resources_dir_path");
353 NormalizePathAndSet(settings_.locales_dir_path, "locales_dir_path");
354
355 browser_info_manager_.reset(new CefBrowserInfoManager);
356
357 main_runner_.reset(new CefMainRunner(settings_.multi_threaded_message_loop,
358 settings_.external_message_pump));
359 return main_runner_->Initialize(
360 &settings_, application, args, windows_sandbox_info, &initialized_,
361 base::BindOnce(&CefContext::OnContextInitialized,
362 base::Unretained(this)));
363 }
364
RunMessageLoop()365 void CefContext::RunMessageLoop() {
366 // Must always be called on the same thread as Initialize.
367 DCHECK(OnInitThread());
368
369 // Blocks until QuitMessageLoop() is called.
370 main_runner_->RunMessageLoop();
371 }
372
QuitMessageLoop()373 void CefContext::QuitMessageLoop() {
374 // Must always be called on the same thread as Initialize.
375 DCHECK(OnInitThread());
376
377 main_runner_->QuitMessageLoop();
378 }
379
Shutdown()380 void CefContext::Shutdown() {
381 // Must always be called on the same thread as Initialize.
382 DCHECK(OnInitThread());
383
384 shutting_down_ = true;
385
386 main_runner_->Shutdown(
387 base::BindOnce(&CefContext::ShutdownOnUIThread, base::Unretained(this)),
388 base::BindOnce(&CefContext::FinalizeShutdown, base::Unretained(this)));
389 }
390
OnInitThread()391 bool CefContext::OnInitThread() {
392 return (base::PlatformThread::CurrentId() == init_thread_id_);
393 }
394
GetBackgroundColor(const CefBrowserSettings * browser_settings,cef_state_t windowless_state) const395 SkColor CefContext::GetBackgroundColor(
396 const CefBrowserSettings* browser_settings,
397 cef_state_t windowless_state) const {
398 bool is_windowless = windowless_state == STATE_ENABLED
399 ? true
400 : (windowless_state == STATE_DISABLED
401 ? false
402 : !!settings_.windowless_rendering_enabled);
403
404 // Default to opaque white if no acceptable color values are found.
405 SkColor sk_color = SK_ColorWHITE;
406
407 if (!browser_settings ||
408 !GetColor(browser_settings->background_color, is_windowless, &sk_color)) {
409 GetColor(settings_.background_color, is_windowless, &sk_color);
410 }
411 return sk_color;
412 }
413
GetTraceSubscriber()414 CefTraceSubscriber* CefContext::GetTraceSubscriber() {
415 CEF_REQUIRE_UIT();
416 if (shutting_down_)
417 return nullptr;
418 if (!trace_subscriber_.get())
419 trace_subscriber_.reset(new CefTraceSubscriber());
420 return trace_subscriber_.get();
421 }
422
PopulateGlobalRequestContextSettings(CefRequestContextSettings * settings)423 void CefContext::PopulateGlobalRequestContextSettings(
424 CefRequestContextSettings* settings) {
425 CefRefPtr<CefCommandLine> command_line =
426 CefCommandLine::GetGlobalCommandLine();
427
428 // This value was already normalized in Initialize.
429 CefString(&settings->cache_path) = CefString(&settings_.cache_path);
430
431 settings->persist_session_cookies =
432 settings_.persist_session_cookies ||
433 command_line->HasSwitch(switches::kPersistSessionCookies);
434 settings->persist_user_preferences =
435 settings_.persist_user_preferences ||
436 command_line->HasSwitch(switches::kPersistUserPreferences);
437
438 CefString(&settings->cookieable_schemes_list) =
439 CefString(&settings_.cookieable_schemes_list);
440 settings->cookieable_schemes_exclude_defaults =
441 settings_.cookieable_schemes_exclude_defaults;
442 }
443
NormalizeRequestContextSettings(CefRequestContextSettings * settings)444 void CefContext::NormalizeRequestContextSettings(
445 CefRequestContextSettings* settings) {
446 // The |root_cache_path| value was already normalized in Initialize.
447 const base::FilePath& root_cache_path = CefString(&settings_.root_cache_path);
448 NormalizeCachePathAndSet(settings->cache_path, root_cache_path);
449 }
450
AddObserver(Observer * observer)451 void CefContext::AddObserver(Observer* observer) {
452 CEF_REQUIRE_UIT();
453 observers_.AddObserver(observer);
454 }
455
RemoveObserver(Observer * observer)456 void CefContext::RemoveObserver(Observer* observer) {
457 CEF_REQUIRE_UIT();
458 observers_.RemoveObserver(observer);
459 }
460
HasObserver(Observer * observer) const461 bool CefContext::HasObserver(Observer* observer) const {
462 CEF_REQUIRE_UIT();
463 return observers_.HasObserver(observer);
464 }
465
OnContextInitialized()466 void CefContext::OnContextInitialized() {
467 CEF_REQUIRE_UIT();
468
469 if (application_) {
470 // Notify the handler after the global browser context has initialized.
471 CefRefPtr<CefRequestContext> request_context =
472 CefRequestContext::GetGlobalContext();
473 auto impl = static_cast<CefRequestContextImpl*>(request_context.get());
474 impl->ExecuteWhenBrowserContextInitialized(base::BindOnce(
475 [](CefRefPtr<CefApp> app) {
476 CefRefPtr<CefBrowserProcessHandler> handler =
477 app->GetBrowserProcessHandler();
478 if (handler)
479 handler->OnContextInitialized();
480 },
481 application_));
482 }
483 }
484
ShutdownOnUIThread()485 void CefContext::ShutdownOnUIThread() {
486 CEF_REQUIRE_UIT();
487
488 browser_info_manager_->DestroyAllBrowsers();
489
490 for (auto& observer : observers_)
491 observer.OnContextDestroyed();
492
493 if (trace_subscriber_.get())
494 trace_subscriber_.reset(nullptr);
495 }
496
FinalizeShutdown()497 void CefContext::FinalizeShutdown() {
498 browser_info_manager_.reset(nullptr);
499 application_ = nullptr;
500 }
501