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 defined(OS_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 defined(OS_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 // defined(OS_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 defined(OS_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 defined(OS_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 defined(OS_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 defined(OS_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 base::CurrentThread::Get()->set_os_modal_loop(osModalLoop);
299 else
300 CEF_POST_TASK(CEF_UIT, base::Bind(CefSetOSModalLoop, osModalLoop));
301 #endif // defined(OS_WIN)
302 }
303
304 // CefContext
305
CefContext()306 CefContext::CefContext()
307 : initialized_(false), shutting_down_(false), init_thread_id_(0) {}
308
~CefContext()309 CefContext::~CefContext() {}
310
311 // static
Get()312 CefContext* CefContext::Get() {
313 return g_context;
314 }
315
Initialize(const CefMainArgs & args,const CefSettings & settings,CefRefPtr<CefApp> application,void * windows_sandbox_info)316 bool CefContext::Initialize(const CefMainArgs& args,
317 const CefSettings& settings,
318 CefRefPtr<CefApp> application,
319 void* windows_sandbox_info) {
320 init_thread_id_ = base::PlatformThread::CurrentId();
321 settings_ = settings;
322 application_ = application;
323
324 #if !(defined(OS_WIN) || defined(OS_LINUX))
325 if (settings.multi_threaded_message_loop) {
326 NOTIMPLEMENTED() << "multi_threaded_message_loop is not supported.";
327 return false;
328 }
329 #endif
330
331 #if defined(OS_WIN)
332 // Signal Chrome Elf that Chrome has begun to start.
333 SignalChromeElf();
334 #endif
335
336 const base::FilePath& root_cache_path =
337 NormalizePathAndSet(settings_.root_cache_path, "root_cache_path");
338 const base::FilePath& cache_path =
339 NormalizeCachePathAndSet(settings_.cache_path, root_cache_path);
340 if (root_cache_path.empty() && !cache_path.empty()) {
341 CefString(&settings_.root_cache_path) = cache_path.value();
342 }
343
344 // All other paths that need to be normalized.
345 NormalizePathAndSet(settings_.browser_subprocess_path,
346 "browser_subprocess_path");
347 NormalizePathAndSet(settings_.framework_dir_path, "framework_dir_path");
348 NormalizePathAndSet(settings_.main_bundle_path, "main_bundle_path");
349 NormalizePathAndSet(settings_.user_data_path, "user_data_path");
350 NormalizePathAndSet(settings_.resources_dir_path, "resources_dir_path");
351 NormalizePathAndSet(settings_.locales_dir_path, "locales_dir_path");
352
353 browser_info_manager_.reset(new CefBrowserInfoManager);
354
355 main_runner_.reset(new CefMainRunner(settings_.multi_threaded_message_loop,
356 settings_.external_message_pump));
357 return main_runner_->Initialize(
358 &settings_, application, args, windows_sandbox_info, &initialized_,
359 base::BindOnce(&CefContext::OnContextInitialized,
360 base::Unretained(this)));
361 }
362
RunMessageLoop()363 void CefContext::RunMessageLoop() {
364 // Must always be called on the same thread as Initialize.
365 DCHECK(OnInitThread());
366
367 // Blocks until QuitMessageLoop() is called.
368 main_runner_->RunMessageLoop();
369 }
370
QuitMessageLoop()371 void CefContext::QuitMessageLoop() {
372 // Must always be called on the same thread as Initialize.
373 DCHECK(OnInitThread());
374
375 main_runner_->QuitMessageLoop();
376 }
377
Shutdown()378 void CefContext::Shutdown() {
379 // Must always be called on the same thread as Initialize.
380 DCHECK(OnInitThread());
381
382 shutting_down_ = true;
383
384 main_runner_->Shutdown(
385 base::BindOnce(&CefContext::ShutdownOnUIThread, base::Unretained(this)),
386 base::BindOnce(&CefContext::FinalizeShutdown, base::Unretained(this)));
387 }
388
OnInitThread()389 bool CefContext::OnInitThread() {
390 return (base::PlatformThread::CurrentId() == init_thread_id_);
391 }
392
GetBackgroundColor(const CefBrowserSettings * browser_settings,cef_state_t windowless_state) const393 SkColor CefContext::GetBackgroundColor(
394 const CefBrowserSettings* browser_settings,
395 cef_state_t windowless_state) const {
396 bool is_windowless = windowless_state == STATE_ENABLED
397 ? true
398 : (windowless_state == STATE_DISABLED
399 ? false
400 : !!settings_.windowless_rendering_enabled);
401
402 // Default to opaque white if no acceptable color values are found.
403 SkColor sk_color = SK_ColorWHITE;
404
405 if (!browser_settings ||
406 !GetColor(browser_settings->background_color, is_windowless, &sk_color)) {
407 GetColor(settings_.background_color, is_windowless, &sk_color);
408 }
409 return sk_color;
410 }
411
GetTraceSubscriber()412 CefTraceSubscriber* CefContext::GetTraceSubscriber() {
413 CEF_REQUIRE_UIT();
414 if (shutting_down_)
415 return nullptr;
416 if (!trace_subscriber_.get())
417 trace_subscriber_.reset(new CefTraceSubscriber());
418 return trace_subscriber_.get();
419 }
420
PopulateGlobalRequestContextSettings(CefRequestContextSettings * settings)421 void CefContext::PopulateGlobalRequestContextSettings(
422 CefRequestContextSettings* settings) {
423 CefRefPtr<CefCommandLine> command_line =
424 CefCommandLine::GetGlobalCommandLine();
425
426 // This value was already normalized in Initialize.
427 CefString(&settings->cache_path) = CefString(&settings_.cache_path);
428
429 settings->persist_session_cookies =
430 settings_.persist_session_cookies ||
431 command_line->HasSwitch(switches::kPersistSessionCookies);
432 settings->persist_user_preferences =
433 settings_.persist_user_preferences ||
434 command_line->HasSwitch(switches::kPersistUserPreferences);
435 settings->ignore_certificate_errors =
436 settings_.ignore_certificate_errors ||
437 command_line->HasSwitch(switches::kIgnoreCertificateErrors);
438
439 CefString(&settings->cookieable_schemes_list) =
440 CefString(&settings_.cookieable_schemes_list);
441 settings->cookieable_schemes_exclude_defaults =
442 settings_.cookieable_schemes_exclude_defaults;
443 }
444
NormalizeRequestContextSettings(CefRequestContextSettings * settings)445 void CefContext::NormalizeRequestContextSettings(
446 CefRequestContextSettings* settings) {
447 // The |root_cache_path| value was already normalized in Initialize.
448 const base::FilePath& root_cache_path = CefString(&settings_.root_cache_path);
449 NormalizeCachePathAndSet(settings->cache_path, root_cache_path);
450 }
451
AddObserver(Observer * observer)452 void CefContext::AddObserver(Observer* observer) {
453 CEF_REQUIRE_UIT();
454 observers_.AddObserver(observer);
455 }
456
RemoveObserver(Observer * observer)457 void CefContext::RemoveObserver(Observer* observer) {
458 CEF_REQUIRE_UIT();
459 observers_.RemoveObserver(observer);
460 }
461
HasObserver(Observer * observer) const462 bool CefContext::HasObserver(Observer* observer) const {
463 CEF_REQUIRE_UIT();
464 return observers_.HasObserver(observer);
465 }
466
OnContextInitialized()467 void CefContext::OnContextInitialized() {
468 CEF_REQUIRE_UIT();
469
470 if (application_) {
471 // Notify the handler after the global browser context has initialized.
472 CefRefPtr<CefRequestContext> request_context =
473 CefRequestContext::GetGlobalContext();
474 auto impl = static_cast<CefRequestContextImpl*>(request_context.get());
475 impl->ExecuteWhenBrowserContextInitialized(base::BindOnce(
476 [](CefRefPtr<CefApp> app) {
477 CefRefPtr<CefBrowserProcessHandler> handler =
478 app->GetBrowserProcessHandler();
479 if (handler)
480 handler->OnContextInitialized();
481 },
482 application_));
483 }
484 }
485
ShutdownOnUIThread()486 void CefContext::ShutdownOnUIThread() {
487 CEF_REQUIRE_UIT();
488
489 browser_info_manager_->DestroyAllBrowsers();
490
491 for (auto& observer : observers_)
492 observer.OnContextDestroyed();
493
494 if (trace_subscriber_.get())
495 trace_subscriber_.reset(nullptr);
496 }
497
FinalizeShutdown()498 void CefContext::FinalizeShutdown() {
499 browser_info_manager_.reset(nullptr);
500 application_ = nullptr;
501 }
502