diff --git a/BUILD.gn b/BUILD.gn index 05ec282a5..aa3abb7fc 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -426,6 +426,8 @@ static_library("libcef_static") { "libcef/common/content_client.h", "libcef/common/crash_reporter_client.cc", "libcef/common/crash_reporter_client.h", + "libcef/common/crash_reporting.cc", + "libcef/common/crash_reporting.h", "libcef/common/drag_data_impl.cc", "libcef/common/drag_data_impl.h", "libcef/common/extensions/chrome_generated_schemas.cc", @@ -558,6 +560,11 @@ static_library("libcef_static") { "//third_party/WebKit/public/web", ] + public_deps = [ + # Bring in feature flag defines. + "//cef/libcef/features", + ] + deps = [ # Generate pack files and associated CEF header files. ":make_pack_header_resources", @@ -679,6 +686,8 @@ static_library("libcef_static") { "libcef/browser/osr/browser_platform_delegate_osr_win.cc", "libcef/browser/osr/browser_platform_delegate_osr_win.h", "libcef/browser/osr/render_widget_host_view_osr_win.cc", + "libcef/common/crash_reporting_win.cc", + "libcef/common/crash_reporting_win.h", # Part of //chrome/utility. "//chrome/utility/printing_handler.cc", @@ -687,7 +696,6 @@ static_library("libcef_static") { deps += [ "//chrome_elf", - "//components/crash/content/app:run_as_crashpad_handler", ] } @@ -730,6 +738,10 @@ static_library("libcef_static") { ] } + if (is_win || is_mac) { + deps += [ "//third_party/crashpad/crashpad/handler:handler_lib" ] + } + if (use_x11) { deps += [ "//ui/events/devices/x11" ] } diff --git a/cef_paths.gypi b/cef_paths.gypi index c8da99593..fd396b44a 100644 --- a/cef_paths.gypi +++ b/cef_paths.gypi @@ -21,6 +21,7 @@ 'include/cef_command_line.h', 'include/cef_context_menu_handler.h', 'include/cef_cookie.h', + 'include/cef_crash_util.h', 'include/cef_dialog_handler.h', 'include/cef_display_handler.h', 'include/cef_dom.h', @@ -108,6 +109,7 @@ 'include/capi/cef_command_line_capi.h', 'include/capi/cef_context_menu_handler_capi.h', 'include/capi/cef_cookie_capi.h', + 'include/capi/cef_crash_util_capi.h', 'include/capi/cef_dialog_handler_capi.h', 'include/capi/cef_display_handler_capi.h', 'include/capi/cef_dom_capi.h', diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 379f982b7..bb7422a3e 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -193,6 +193,8 @@ 'tests/cefclient/browser/bytes_write_handler.cc', 'tests/cefclient/browser/bytes_write_handler.h', 'tests/cefclient/browser/client_app_delegates_browser.cc', + 'tests/cefclient/browser/client_browser.cc', + 'tests/cefclient/browser/client_browser.h', 'tests/cefclient/browser/client_handler.cc', 'tests/cefclient/browser/client_handler.h', 'tests/cefclient/browser/client_handler_osr.cc', diff --git a/include/capi/cef_crash_util_capi.h b/include/capi/cef_crash_util_capi.h new file mode 100644 index 000000000..1b8992454 --- /dev/null +++ b/include/capi/cef_crash_util_capi.h @@ -0,0 +1,136 @@ +// Copyright (c) 2017 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool and should not edited +// by hand. See the translator.README.txt file in the tools directory for +// more information. +// + +#ifndef CEF_INCLUDE_CAPI_CEF_CRASH_UTIL_CAPI_H_ +#define CEF_INCLUDE_CAPI_CEF_CRASH_UTIL_CAPI_H_ +#pragma once + +#include "include/capi/cef_base_capi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/// +// Crash reporting is configured using an INI-style config file named +// "crash_reporter.cfg". On Windows and Linux this file must be placed next to +// the main application executable. On macOS this file must be placed in the +// top-level app bundle Resources directory (e.g. +// ".app/Contents/Resources"). File contents are as follows: +// +// # Comments start with a hash character and must be on their own line. +// +// [Config] +// AppName= +// ExternalHandler= +// ServerURL= +// RateLimitEnabled= +// MaxUploadsPerDay= +// MaxDatabaseSizeInMb= +// MaxDatabaseAgeInDays= +// +// [CrashKeys] +// my_key1= +// my_key2= +// +// Config section: +// +// If "AppName" is set on Windows then crash report information (metrics, +// database and dumps) will be stored locally on disk under the +// "C:\Users\[CurrentUser]\AppData\Local\[AppName]\User Data" folder. On other +// platforms the CefSettings.user_data_path value will be used. +// +// If "ExternalHandler" is set on Windows then the specified exe will be +// launched as the crashpad-handler instead of re-launching the main process +// exe. The value can be an absolute path or a path relative to the main exe +// directory. On Linux the CefSettings.browser_subprocess_path value will be +// used. On macOS the existing subprocess app bundle will be used. +// +// If "ServerURL" is set then crashes will be uploaded as a multi-part POST +// request to the specified URL. Otherwise, reports will only be stored locally +// on disk. +// +// If "RateLimitEnabled" is set to true (1) then crash report uploads will be +// rate limited as follows: +// 1. If "MaxUploadsPerDay" is set to a positive value then at most the +// specified number of crashes will be uploaded in each 24 hour period. +// 2. If crash upload fails due to a network or server error then an +// incremental backoff delay up to a maximum of 24 hours will be applied for +// retries. +// 3. If a backoff delay is applied and "MaxUploadsPerDay" is > 1 then the +// "MaxUploadsPerDay" value will be reduced to 1 until the client is +// restarted. This helps to avoid an upload flood when the network or +// server error is resolved. +// Rate limiting is not supported on Linux. +// +// If "MaxDatabaseSizeInMb" is set to a positive value then crash report storage +// on disk will be limited to that size in megabytes. For example, on Windows +// each dump is about 600KB so a "MaxDatabaseSizeInMb" value of 20 equates to +// about 34 crash reports stored on disk. Not supported on Linux. +// +// If "MaxDatabaseAgeInDays" is set to a positive value then crash reports older +// than the specified age in days will be deleted. Not supported on Linux. +// +// CrashKeys section: +// +// Any number of crash keys can be specified for use by the application. Crash +// key values will be truncated based on the specified size (small = 63 bytes, +// medium = 252 bytes, large = 1008 bytes). The value of crash keys can be set +// from any thread or process using the CefSetCrashKeyValue function. These +// key/value pairs will be sent to the crash server along with the crash dump +// file. Medium and large values will be chunked for submission. For example, if +// your key is named "mykey" then the value will be broken into ordered chunks +// and submitted using keys named "mykey-1", "mykey-2", etc. +/// +CEF_EXPORT int cef_crash_reporting_enabled(); + +/// +// Sets or clears a specific key-value pair from the crash metadata. +/// +CEF_EXPORT void cef_set_crash_key_value(const cef_string_t* key, + const cef_string_t* value); + +#ifdef __cplusplus +} +#endif + +#endif // CEF_INCLUDE_CAPI_CEF_CRASH_UTIL_CAPI_H_ diff --git a/include/cef_crash_util.h b/include/cef_crash_util.h new file mode 100644 index 000000000..39ec6952d --- /dev/null +++ b/include/cef_crash_util.h @@ -0,0 +1,128 @@ +// Copyright (c) 2016 Marshall A. Greenblatt. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the name Chromium Embedded +// Framework nor the names of its contributors may be used to endorse +// or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// --------------------------------------------------------------------------- +// +// The contents of this file must follow a specific format in order to +// support the CEF translator tool. See the translator.README.txt file in the +// tools directory for more information. +// + +#ifndef CEF_INCLUDE_CEF_CRASH_UTIL_H_ +#define CEF_INCLUDE_CEF_CRASH_UTIL_H_ +#pragma once + +/// +// Crash reporting is configured using an INI-style config file named +// "crash_reporter.cfg". On Windows and Linux this file must be placed next to +// the main application executable. On macOS this file must be placed in the +// top-level app bundle Resources directory (e.g. +// ".app/Contents/Resources"). File contents are as follows: +// +// # Comments start with a hash character and must be on their own line. +// +// [Config] +// AppName= +// ExternalHandler= +// ServerURL= +// RateLimitEnabled= +// MaxUploadsPerDay= +// MaxDatabaseSizeInMb= +// MaxDatabaseAgeInDays= +// +// [CrashKeys] +// my_key1= +// my_key2= +// +// Config section: +// +// If "AppName" is set on Windows then crash report information (metrics, +// database and dumps) will be stored locally on disk under the +// "C:\Users\[CurrentUser]\AppData\Local\[AppName]\User Data" folder. On other +// platforms the CefSettings.user_data_path value will be used. +// +// If "ExternalHandler" is set on Windows then the specified exe will be +// launched as the crashpad-handler instead of re-launching the main process +// exe. The value can be an absolute path or a path relative to the main exe +// directory. On Linux the CefSettings.browser_subprocess_path value will be +// used. On macOS the existing subprocess app bundle will be used. +// +// If "ServerURL" is set then crashes will be uploaded as a multi-part POST +// request to the specified URL. Otherwise, reports will only be stored locally +// on disk. +// +// If "RateLimitEnabled" is set to true then crash report uploads will be rate +// limited as follows: +// 1. If "MaxUploadsPerDay" is set to a positive value then at most the +// specified number of crashes will be uploaded in each 24 hour period. +// 2. If crash upload fails due to a network or server error then an +// incremental backoff delay up to a maximum of 24 hours will be applied for +// retries. +// 3. If a backoff delay is applied and "MaxUploadsPerDay" is > 1 then the +// "MaxUploadsPerDay" value will be reduced to 1 until the client is +// restarted. This helps to avoid an upload flood when the network or +// server error is resolved. +// Rate limiting is not supported on Linux. +// +// If "MaxDatabaseSizeInMb" is set to a positive value then crash report storage +// on disk will be limited to that size in megabytes. For example, on Windows +// each dump is about 600KB so a "MaxDatabaseSizeInMb" value of 20 equates to +// about 34 crash reports stored on disk. Not supported on Linux. +// +// If "MaxDatabaseAgeInDays" is set to a positive value then crash reports older +// than the specified age in days will be deleted. Not supported on Linux. +// +// CrashKeys section: +// +// Any number of crash keys can be specified for use by the application. Crash +// key values will be truncated based on the specified size (small = 63 bytes, +// medium = 252 bytes, large = 1008 bytes). The value of crash keys can be set +// from any thread or process using the CefSetCrashKeyValue function. These +// key/value pairs will be sent to the crash server along with the crash dump +// file. Medium and large values will be chunked for submission. For example, +// if your key is named "mykey" then the value will be broken into ordered +// chunks and submitted using keys named "mykey-1", "mykey-2", etc. +/// +/*--cef()--*/ +bool CefCrashReportingEnabled(); + +#include "include/cef_base.h" + +/// +// Sets or clears a specific key-value pair from the crash metadata. +/// +/*--cef()--*/ +void CefSetCrashKeyValue(const CefString& key, const CefString& value); + +#endif // CEF_INCLUDE_CEF_CRASH_UTIL_H_ diff --git a/libcef/browser/content_browser_client.cc b/libcef/browser/content_browser_client.cc index ac2513279..abd3b21db 100644 --- a/libcef/browser/content_browser_client.cc +++ b/libcef/browser/content_browser_client.cc @@ -89,6 +89,7 @@ #if defined(OS_POSIX) && !defined(OS_MACOSX) #include "base/debug/leak_annotations.h" +#include "chrome/common/chrome_paths.h" #include "components/crash/content/app/breakpad_linux.h" #include "components/crash/content/browser/crash_handler_host_linux.h" #include "content/public/common/content_descriptors.h" @@ -317,14 +318,15 @@ class CefQuotaPermissionContext : public content::QuotaPermissionContext { #if defined(OS_POSIX) && !defined(OS_MACOSX) breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost( const std::string& process_type) { - base::FilePath dumps_path = - base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( - switches::kCrashDumpsDir); + base::FilePath dumps_path; + PathService::Get(chrome::DIR_CRASH_DUMPS, &dumps_path); { ANNOTATE_SCOPED_MEMORY_LEAK; + // Uploads will only occur if a non-empty crash URL is specified in + // CefMainDelegate::InitCrashReporter. breakpad::CrashHandlerHostLinux* crash_handler = new breakpad::CrashHandlerHostLinux( - process_type, dumps_path, false); + process_type, dumps_path, true /* upload */); crash_handler->StartUploaderThread(); return crash_handler; } @@ -618,11 +620,7 @@ void CefContentBrowserClient::AppendExtraCommandLineSwitches( // Propagate the following switches to all command lines (along with any // associated values) if present in the browser command line. static const char* const kSwitchNames[] = { -#if !defined(OS_WIN) - switches::kCrashDumpsDir, -#endif switches::kDisablePackLoading, - switches::kEnableCrashReporter, switches::kLang, switches::kLocalesDirPath, switches::kLogFile, diff --git a/libcef/browser/context.cc b/libcef/browser/context.cc index 121282dc5..7071d51e0 100644 --- a/libcef/browser/context.cc +++ b/libcef/browser/context.cc @@ -31,14 +31,18 @@ #include "ui/base/ui_base_switches.h" #if defined(OS_WIN) +#include "base/strings/utf_string_conversions.h" #include "chrome_elf/chrome_elf_main.h" #include "content/public/app/sandbox_helper_win.h" -#include "components/crash/content/app/crash_switches.h" #include "components/crash/content/app/crashpad.h" -#include "components/crash/content/app/run_as_crashpad_handler_win.h" #include "sandbox/win/src/sandbox_types.h" #endif +#if defined(OS_MACOSX) || defined(OS_WIN) +#include "components/crash/content/app/crash_switches.h" +#include "third_party/crashpad/crashpad/handler/handler_main.h" +#endif + namespace { CefContext* g_context = NULL; @@ -73,7 +77,7 @@ void DisableFMA3() { // Signal chrome_elf to initialize crash reporting, rather than doing it in // DllMain. See https://crbug.com/656800 for details. -void InitializeCrashReporting() { +void InitCrashReporter() { static bool initialized = false; if (initialized) return; @@ -82,6 +86,47 @@ void InitializeCrashReporting() { } #endif // defined(OS_WIN) +#if defined(OS_MACOSX) || defined(OS_WIN) + +// Based on components/crash/content/app/run_as_crashpad_handler_win.cc +// Remove the "--type=crashpad-handler" command-line flag that will otherwise +// confuse the crashpad handler. +// Chrome uses an embedded crashpad handler on Windows only and imports this +// function via the existing "run_as_crashpad_handler" target defined in +// components/crash/content/app/BUILD.gn. CEF uses an embedded handler on both +// Windows and macOS so we define the function here instead of using the +// existing target (because we can't use that target on macOS). +int RunAsCrashpadHandler(const base::CommandLine& command_line) { + base::CommandLine::StringVector argv = command_line.argv(); + const base::CommandLine::StringType process_type = + FILE_PATH_LITERAL("--type="); + argv.erase(std::remove_if(argv.begin(), argv.end(), + [&process_type](const base::CommandLine::StringType& str) { + return base::StartsWith(str, process_type, + base::CompareCase::SENSITIVE) || + (!str.empty() && str[0] == L'/'); + }), + argv.end()); + + std::unique_ptr argv_as_utf8(new char*[argv.size() + 1]); + std::vector storage; + storage.reserve(argv.size()); + for (size_t i = 0; i < argv.size(); ++i) { +#if defined(OS_WIN) + storage.push_back(base::UTF16ToUTF8(argv[i])); +#else + storage.push_back(argv[i]); +#endif + argv_as_utf8[i] = &storage[i][0]; + } + argv_as_utf8[argv.size()] = nullptr; + argv.clear(); + return crashpad::HandlerMain(static_cast(storage.size()), + argv_as_utf8.get()); +} + +#endif // defined(OS_MACOSX) || defined(OS_WIN) + } // namespace int CefExecuteProcess(const CefMainArgs& args, @@ -91,7 +136,7 @@ int CefExecuteProcess(const CefMainArgs& args, #if defined(ARCH_CPU_X86_64) DisableFMA3(); #endif - InitializeCrashReporting(); + InitCrashReporter(); #endif base::CommandLine command_line(base::CommandLine::NO_PROGRAM); @@ -112,9 +157,9 @@ int CefExecuteProcess(const CefMainArgs& args, if (process_type.empty()) return -1; -#if defined(OS_WIN) +#if defined(OS_MACOSX) || defined(OS_WIN) if (process_type == crash_reporter::switches::kCrashpadHandler) - return crash_reporter::RunAsCrashpadHandler(command_line); + return RunAsCrashpadHandler(command_line); #endif CefMainDelegate main_delegate(application); @@ -150,7 +195,7 @@ bool CefInitialize(const CefMainArgs& args, #if defined(ARCH_CPU_X86_64) DisableFMA3(); #endif - InitializeCrashReporting(); + InitCrashReporter(); #endif // Return true if the global context already exists. diff --git a/libcef/browser/net/chrome_scheme_handler.cc b/libcef/browser/net/chrome_scheme_handler.cc index a7f744577..19481bb6e 100644 --- a/libcef/browser/net/chrome_scheme_handler.cc +++ b/libcef/browser/net/chrome_scheme_handler.cc @@ -32,6 +32,7 @@ #include "chrome/browser/browser_about_handler.h" #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h" #include "chrome/common/url_constants.h" +#include "content/browser/frame_host/debug_urls.h" #include "content/browser/webui/content_web_ui_controller_factory.h" #include "content/public/browser/browser_url_handler.h" #include "content/public/common/url_constants.h" @@ -74,6 +75,13 @@ const char* kAllowedWebUIHosts[] = { content::kChromeUIWebRTCInternalsHost, }; +// Hosts that don't have useful output when linked directly. They'll be excluded +// from the "chrome://webui-hosts" listing. +const char* kUnlistedHosts[] = { + content::kChromeUINetworkErrorHost, + content::kChromeUIResourcesHost, +}; + enum ChromeHostId { CHROME_UNKNOWN = 0, CHROME_LICENSE, @@ -103,18 +111,87 @@ ChromeHostId GetChromeHostId(const std::string& host) { return CHROME_UNKNOWN; } +// Returns CEF and WebUI hosts. Does not include chrome debug hosts (for +// crashing, etc). void GetAllowedHosts(std::vector* hosts) { + // Hosts implemented by CEF. for (size_t i = 0; i < sizeof(kAllowedCefHosts) / sizeof(kAllowedCefHosts[0]); ++i) { hosts->push_back(kAllowedCefHosts[i].host); } + // Explicitly whitelisted WebUI hosts. for (size_t i = 0; i < sizeof(kAllowedWebUIHosts) / sizeof(kAllowedWebUIHosts[0]); ++i) { hosts->push_back(kAllowedWebUIHosts[i]); } } +// Returns true if a host should not be listed on "chrome://webui-hosts". +bool IsUnlistedHost(const std::string& host) { + for (size_t i = 0; + i < sizeof(kUnlistedHosts) / sizeof(kUnlistedHosts[0]); ++i) { + if (host == kUnlistedHosts[i]) + return true; + } + return false; +} + +// Returns true if a host is WebUI and should be allowed to load. +bool IsAllowedWebUIHost(const std::string& host) { + // Explicitly whitelisted WebUI hosts. + for (size_t i = 0; + i < sizeof(kAllowedWebUIHosts) / sizeof(kAllowedWebUIHosts[0]); ++i) { + if (base::EqualsCaseInsensitiveASCII(kAllowedWebUIHosts[i], + host.c_str())) { + return true; + } + } + + return false; +} + +// Additional debug URLs that are not included in chrome::kChromeDebugURLs. +const char* kAllowedDebugURLs[] = { + content::kChromeUIBrowserCrashURL, +}; + +// Returns true for debug URLs that receive special handling (for crashes, etc). +bool IsDebugURL(const GURL& url) { + // URLs handled by the renderer process in + // content/renderer/render_frame_impl.cc MaybeHandleDebugURL(). + if (content::IsRendererDebugURL(url)) + return true; + + // Also include URLs handled by the browser process in + // content/browser/frame_host/debug_urls.cc HandleDebugURL(). + for (int i = 0; i < chrome::kNumberOfChromeDebugURLs; ++i) { + GURL host(chrome::kChromeDebugURLs[i]); + if (url.GetOrigin() == host.GetOrigin()) + return true; + } + + for (size_t i = 0; + i < sizeof(kAllowedDebugURLs) / sizeof(kAllowedDebugURLs[0]); ++i) { + GURL host(kAllowedDebugURLs[i]); + if (url.GetOrigin() == host.GetOrigin()) + return true; + } + + return false; +} + +void GetDebugURLs(std::vector* urls) { + for (int i = 0; i < chrome::kNumberOfChromeDebugURLs; ++i) { + urls->push_back(chrome::kChromeDebugURLs[i]); + } + + for (size_t i = 0; + i < sizeof(kAllowedDebugURLs) / sizeof(kAllowedDebugURLs[0]); ++i) { + urls->push_back(kAllowedDebugURLs[i]); + } +} + // Intercepts all WebUI calls and either blocks them or forwards them to the // Content or Chrome WebUI factory as appropriate. class CefWebUIControllerFactory : public content::WebUIControllerFactory { @@ -124,13 +201,8 @@ class CefWebUIControllerFactory : public content::WebUIControllerFactory { if (!url.SchemeIs(content::kChromeUIScheme)) return false; - for (size_t i = 0; - i < sizeof(kAllowedWebUIHosts) / sizeof(kAllowedWebUIHosts[0]); ++i) { - if (base::EqualsCaseInsensitiveASCII(kAllowedWebUIHosts[i], - url.host().c_str())) { - return true; - } - } + if (IsAllowedWebUIHost(url.host())) + return true; return false; } @@ -534,16 +606,32 @@ class Delegate : public InternalHandlerDelegate { std::string html = "\nWebUI Hosts\n" "

WebUI Hosts

\n
    \n"; - std::vector hosts; - GetAllowedHosts(&hosts); - std::sort(hosts.begin(), hosts.end()); + std::vector list; + GetAllowedHosts(&list); + std::sort(list.begin(), list.end()); - for (size_t i = 0U; i < hosts.size(); ++i) { - html += "
  • chrome://" + - hosts[i] + "
  • \n"; + for (size_t i = 0U; i < list.size(); ++i) { + if (IsUnlistedHost(list[i])) + continue; + + html += "
  • chrome://" + + list[i] + "
  • \n"; } - html += "
\n"; + list.clear(); + GetDebugURLs(&list); + std::sort(list.begin(), list.end()); + + html += "\n

For Debug

\n" + "

The following pages are for debugging purposes only. Because they " + "crash or hang the renderer, they're not linked directly; you can type " + "them into the address bar if you need them.

\n
    \n"; + for (size_t i = 0U; i < list.size(); ++i) { + html += "
  • " + std::string(list[i]) + "
  • \n"; + } + html += "
\n"; + + html += "\n"; action->mime_type = "text/html"; action->stream = CefStreamReader::CreateForData( @@ -604,6 +692,10 @@ class ChromeProtocolHandlerWrapper : net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const override { + // Don't handle debug URLs. + if (IsDebugURL(request->url())) + return nullptr; + // Only allow WebUI to handle chrome:// URLs whitelisted by CEF. if (CefWebUIControllerFactory::AllowWebUIForURL(request->url())) { return chrome_protocol_handler_->MaybeCreateJob(request, diff --git a/libcef/common/cef_crash_report_upload_thread.cc b/libcef/common/cef_crash_report_upload_thread.cc new file mode 100644 index 000000000..d9145cf50 --- /dev/null +++ b/libcef/common/cef_crash_report_upload_thread.cc @@ -0,0 +1,247 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "libcef/common/cef_crash_report_upload_thread.h" + +#include "third_party/crashpad/crashpad/client/settings.h" + +using namespace crashpad; + +namespace { + +// Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to +// false upon destruction unless disarmed by calling Fire() or Disarm(). Fire() +// triggers an immediate call. Armed upon construction. +class CallRecordUploadAttempt { + public: + CallRecordUploadAttempt(CrashReportDatabase* database, + const CrashReportDatabase::Report* report) + : database_(database), + report_(report) { + } + + ~CallRecordUploadAttempt() { + Fire(); + } + + void Fire() { + if (report_) { + database_->RecordUploadAttempt(report_, false, std::string()); + } + + Disarm(); + } + + void Disarm() { + report_ = nullptr; + } + + private: + CrashReportDatabase* database_; // weak + const CrashReportDatabase::Report* report_; // weak + + DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt); +}; + +} // namespace + +CefCrashReportUploadThread::CefCrashReportUploadThread( + CrashReportDatabase* database, + const std::string& url, + bool rate_limit, + int max_uploads) + : CrashReportUploadThread(database, url, rate_limit), + max_uploads_(max_uploads) { +} + +CefCrashReportUploadThread::~CefCrashReportUploadThread() { +} + +void CefCrashReportUploadThread::ProcessPendingReports() { + if (BackoffPending()) { + // Try again later. + return; + } + + if (MaxUploadsEnabled()) { + // Retrieve all completed reports. + std::vector reports; + if (database_->GetCompletedReports(&reports) != + CrashReportDatabase::kNoError) { + // The database is sick. It might be prudent to stop trying to poke it + // from this thread by abandoning the thread altogether. On the other + // hand, if the problem is transient, it might be possible to talk to it + // again on the next pass. For now, take the latter approach. + return; + } + + const time_t now = time(nullptr); + const int kSeconds = 60 * 60 * 24; // 24 hours + + // Count how many reports have completed in the last 24 hours. + recent_upload_ct_ = 0; + for (const CrashReportDatabase::Report& report : reports) { + if (report.last_upload_attempt_time > now - kSeconds) + recent_upload_ct_++; + } + } + + // Continue with processing pending reports. + CrashReportUploadThread::ProcessPendingReports(); +} + +void CefCrashReportUploadThread::ProcessPendingReport( + const CrashReportDatabase::Report& report) { + // Always allow upload if it's been explicitly requested by the user. + if (!report.upload_explicitly_requested) { + if (!UploadsEnabled()) { + // Don’t attempt an upload if there’s no URL or if uploads have been + // disabled in the database’s settings. + database_->SkipReportUpload( + report.uuid, Metrics::CrashSkippedReason::kUploadsDisabled); + return; + } + + if (MaxUploadsExceeded()) { + // Don't send uploads if the rate limit has been exceeded. + database_->SkipReportUpload( + report.uuid, Metrics::CrashSkippedReason::kUploadThrottled); + return; + } + } + + if (BackoffPending()) { + // Try again later. + return; + } + + const CrashReportDatabase::Report* upload_report; + CrashReportDatabase::OperationStatus status = + database_->GetReportForUploading(report.uuid, &upload_report); + switch (status) { + case CrashReportDatabase::kNoError: + break; + + case CrashReportDatabase::kBusyError: + return; + + case CrashReportDatabase::kReportNotFound: + case CrashReportDatabase::kFileSystemError: + case CrashReportDatabase::kDatabaseError: + // In these cases, SkipReportUpload() might not work either, but it’s best + // to at least try to get the report out of the way. + database_->SkipReportUpload(report.uuid, + Metrics::CrashSkippedReason::kDatabaseError); + return; + + case CrashReportDatabase::kCannotRequestUpload: + NOTREACHED(); + return; + } + + CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report); + + std::string response_body; + UploadResult upload_result = UploadReport(upload_report, &response_body); + switch (upload_result) { + case UploadResult::kSuccess: + // The upload completed successfully. + call_record_upload_attempt.Disarm(); + database_->RecordUploadAttempt(upload_report, true, response_body); + if (MaxUploadsEnabled()) + recent_upload_ct_++; + ResetBackoff(); + break; + case UploadResult::kPermanentFailure: + // The upload should never be retried. + call_record_upload_attempt.Fire(); + database_->SkipReportUpload(report.uuid, + Metrics::CrashSkippedReason::kUploadFailed); + break; + case UploadResult::kRetry: + // The upload will be retried after a reasonable backoff delay. Since we + // didn't successfully upload it we won't count it against the rate limit. + IncreaseBackoff(); + break; + } +} + +bool CefCrashReportUploadThread::UploadsEnabled() const { + Settings* const settings = database_->GetSettings(); + bool uploads_enabled; + return !url_.empty() && + settings->GetUploadsEnabled(&uploads_enabled) && uploads_enabled; +} + +bool CefCrashReportUploadThread::MaxUploadsEnabled() const { + return rate_limit_ && max_uploads_ > 0; +} + +bool CefCrashReportUploadThread::MaxUploadsExceeded() const { + return MaxUploadsEnabled() && recent_upload_ct_ >= max_uploads_; +} + +bool CefCrashReportUploadThread::BackoffPending() const { + if (!rate_limit_) + return false; + + Settings* const settings = database_->GetSettings(); + + time_t next_upload_time; + if (settings->GetNextUploadAttemptTime(&next_upload_time) && + next_upload_time > 0) { + const time_t now = time(nullptr); + if (now < next_upload_time) + return true; + } + + return false; +} + +void CefCrashReportUploadThread::IncreaseBackoff() { + if (!rate_limit_) + return; + + const int kHour = 60 * 60; // 1 hour + const int kBackoffSchedule[] = { + kHour / 4, // 15 minutes + kHour, // 1 hour + kHour * 2, // 2 hours + kHour * 4, // 4 hours + kHour * 8, // 8 hours + kHour * 24, // 24 hours + }; + const int kBackoffScheduleSize = + sizeof(kBackoffSchedule) / sizeof(kBackoffSchedule[0]); + + Settings* settings = database_->GetSettings(); + + int backoff_step = 0; + if (settings->GetBackoffStep(&backoff_step) && backoff_step < 0) + backoff_step = 0; + if (++backoff_step > kBackoffScheduleSize) + backoff_step = kBackoffScheduleSize; + + time_t next_upload_time = time(nullptr); // now + next_upload_time += kBackoffSchedule[backoff_step - 1]; + + settings->SetBackoffStep(backoff_step); + settings->SetNextUploadAttemptTime(next_upload_time); + + if (max_uploads_ > 1) { + // If the server is having trouble then we don't want to send many crash + // reports after the backoff expires. Reduce max uploads to 1 per 24 hours + // until the client is restarted. + max_uploads_ = 1; + } +} + +void CefCrashReportUploadThread::ResetBackoff() { + if (!rate_limit_) + return; + + Settings* settings = database_->GetSettings(); + settings->SetBackoffStep(0); + settings->SetNextUploadAttemptTime(0); +} diff --git a/libcef/common/cef_crash_report_upload_thread.h b/libcef/common/cef_crash_report_upload_thread.h new file mode 100644 index 000000000..2c6f0ddbe --- /dev/null +++ b/libcef/common/cef_crash_report_upload_thread.h @@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CEF_LIBCEF_COMMON_CEF_CRASH_REPORT_UPLOAD_THREAD_H_ +#define CEF_LIBCEF_COMMON_CEF_CRASH_REPORT_UPLOAD_THREAD_H_ + +#include "third_party/crashpad/crashpad/handler/crash_report_upload_thread.h" + +class CefCrashReportUploadThread : public crashpad::CrashReportUploadThread { + public: + CefCrashReportUploadThread(crashpad::CrashReportDatabase* database, + const std::string& url, + bool rate_limit, + int max_uploads); + ~CefCrashReportUploadThread(); + + private: + void ProcessPendingReports() override; + void ProcessPendingReport( + const crashpad::CrashReportDatabase::Report& report) override; + + bool UploadsEnabled() const; + + bool MaxUploadsEnabled() const; + bool MaxUploadsExceeded() const; + + bool BackoffPending() const; + void IncreaseBackoff(); + void ResetBackoff(); + + int max_uploads_; + + // Track the number of uploads that have completed within the last 24 hours. + // Only used when RateLimitEnabled() is true. Value is reset each time + // ProcessPendingReports() is called. + int recent_upload_ct_ = 0; + + DISALLOW_COPY_AND_ASSIGN(CefCrashReportUploadThread); +}; + +#endif // CEF_LIBCEF_COMMON_CEF_CRASH_REPORT_UPLOAD_THREAD_H_ diff --git a/libcef/common/cef_switches.cc b/libcef/common/cef_switches.cc index e1d75d069..7c3505b91 100644 --- a/libcef/common/cef_switches.cc +++ b/libcef/common/cef_switches.cc @@ -82,9 +82,6 @@ const char kEnableSpeechInput[] = "enable-speech-input"; // Enable the speech input profanity filter. const char kEnableProfanityFilter[] = "enable-profanity-filter"; -// The directory breakpad should store minidumps in. -const char kCrashDumpsDir[] = "crash-dumps-dir"; - // Disable spell checking. const char kDisableSpellChecking[] = "disable-spell-checking"; diff --git a/libcef/common/cef_switches.h b/libcef/common/cef_switches.h index d6db72393..fd1ae3d65 100644 --- a/libcef/common/cef_switches.h +++ b/libcef/common/cef_switches.h @@ -37,7 +37,6 @@ extern const char kPersistUserPreferences[]; extern const char kEnableMediaStream[]; extern const char kEnableSpeechInput[]; extern const char kEnableProfanityFilter[]; -extern const char kCrashDumpsDir[]; extern const char kDisableSpellChecking[]; extern const char kEnableSpellingService[]; extern const char kOverrideSpellCheckLang[]; diff --git a/libcef/common/crash_reporter_client.cc b/libcef/common/crash_reporter_client.cc index c7c0b47d4..2b8da9fec 100644 --- a/libcef/common/crash_reporter_client.cc +++ b/libcef/common/crash_reporter_client.cc @@ -1,26 +1,465 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2016 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. #include "libcef/common/crash_reporter_client.h" +#include +#include #include -#include "libcef/common/cef_switches.h" -#include "include/cef_version.h" +#if defined(OS_WIN) +#include +#endif -#include "base/command_line.h" #include "base/logging.h" -#include "base/files/file_path.h" #include "base/strings/string16.h" #include "base/strings/stringprintf.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/common/crash_keys.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#else +#include "include/cef_version.h" +#endif + +#if defined(OS_POSIX) +// Don't use CommandLine, FilePath or PathService on Windows. FilePath has +// dependencies outside of kernel32, which is disallowed by chrome_elf. +// CommandLine and PathService depend on global state that will not be +// initialized at the time the CefCrashReporterClient object is created. +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) #include "content/public/common/content_switches.h" +#endif + +#if defined(OS_WIN) +#include "base/debug/leak_annotations.h" +#include "chrome/install_static/install_util.h" +#include "components/crash/content/app/crashpad.h" +#endif + +namespace { + +#if defined(OS_WIN) +typedef base::string16 PathString; +const char kPathSep = '\\'; +#else +typedef std::string PathString; +const char kPathSep = '/'; +#endif + +PathString GetCrashConfigPath() { +#if defined(OS_WIN) + // Start with the path to the running executable. + wchar_t module_path[MAX_PATH]; + if (GetModuleFileName(nullptr, module_path, MAX_PATH) == 0) + return PathString(); + + PathString config_path = module_path; + + // Remove the executable file name. + PathString::size_type last_backslash = + config_path.rfind(kPathSep, config_path.size()); + if (last_backslash != PathString::npos) + config_path.erase(last_backslash + 1); + + config_path += L"crash_reporter.cfg"; + return config_path; +#elif defined(OS_POSIX) + // Start with the path to the running executable. + base::FilePath config_path; + if (!PathService::Get(base::DIR_EXE, &config_path)) + return PathString(); + +#if defined(OS_MACOSX) + // Get the main app bundle path. + config_path = base::mac::GetAppBundlePath(config_path); + if (config_path.empty()) + return PathString(); + + // Go into the Contents/Resources directory. + config_path = config_path.Append(FILE_PATH_LITERAL("Contents")) + .Append(FILE_PATH_LITERAL("Resources")); +#endif + + return config_path.Append(FILE_PATH_LITERAL("crash_reporter.cfg")).value(); +#endif // defined(OS_POSIX) +} + +// On Windows, FAT32 and NTFS both limit filenames to a maximum of 255 +// characters. On POSIX systems, the typical filename length limit is 255 +// character units. HFS+'s limit is actually 255 Unicode characters using +// Apple's modification of Normalization Form D, but the differences aren't +// really worth dealing with here. +const unsigned maxFilenameLength = 255; + +#if defined(OS_WIN) + +const char kInvalidFileChars[] = "<>:\"/\\|?*"; + +bool isInvalidFileCharacter(unsigned char c) { + if (c < ' ' || c == 0x7F) + return true; + for(size_t i = 0; i < sizeof(kInvalidFileChars); ++i) { + if (c == kInvalidFileChars[i]) + return true; + } + return false; +} + +bool isAbsolutePath(const std::string& s) { + // Check for local paths (beginning with "c:\") and network paths + // (beginning with "\\"). + return s.length() > 2 && + ((isalpha(s[0]) && s[1] == ':' && s[2] == kPathSep) || + (s[0] == kPathSep && s[1] == kPathSep)); +} + +std::string extractAbsolutePathStart(std::string& s) { + if (!isAbsolutePath(s)) + return std::string(); + + std::string start; + if (s[0] == kPathSep) { + // Network path. + start = s.substr(0, 2); + s = s.substr(2); + } else { + // Local path. + start = s.substr(0, 3); + s = s.substr(3); + } + return start; +} + +#elif defined(OS_POSIX) + +bool isInvalidFileCharacter(unsigned char c) { + // HFS+ disallows '/' and Linux systems also disallow null. For sanity's sake + // we'll also disallow control characters. + return c < ' ' || c == 0x7F || c == kPathSep; +} + +bool isAbsolutePath(const std::string& s) { + // Check for local paths (beginning with "/") and network paths (beginning + // with "//"). + return s.length() > 1 && s[0] == kPathSep; +} + +std::string extractAbsolutePathStart(std::string& s) { + if (!isAbsolutePath(s)) + return std::string(); + + // May have multiple '/' at the beginning of the path. + std::string start; + do { + s = s.substr(1); + start.push_back(kPathSep); + } while (s.length() > 0 && s[0] == kPathSep); + return start; +} + +#endif // defined(OS_POSIX) + +std::string sanitizePathComponentPart(const std::string& s) { + if (s.empty()) + return std::string(); + + std::string result; + result.reserve(s.length()); + std::remove_copy_if(s.begin(), s.end(), + std::back_inserter(result), + std::not1(std::ptr_fun(isInvalidFileCharacter))); + return result; +} + +std::string sanitizePathComponent(const std::string& s) { + std::string name, ext; + + // Separate name and extension, if any. + std::string::size_type pos = s.rfind('.'); + if (pos != std::string::npos) { + name = s.substr(0, pos); + ext = s.substr(pos + 1); + } else { + name = s; + } + + // Remove invalid characters. + name = sanitizePathComponentPart(name); + ext = sanitizePathComponentPart(ext); + + // Remove a ridiculously-long extension. + if (ext.length() >= maxFilenameLength) + ext = std::string(); + + // Truncate an overly-long filename, reserving one character for a dot. + std::string::size_type max_name_len = maxFilenameLength - ext.length() - 1; + if (name.length() > max_name_len) + name = name.substr(0, max_name_len); + + return ext.empty() ? name : name + "." + ext; +} + +std::string sanitizePath(const std::string& s) { + std::string path = s; + + // Extract the absolute path start component, if any (e.g. "c:\" on Windows). + std::string result = extractAbsolutePathStart(path); + result.reserve(s.length()); + + std::vector parts = + base::SplitString(path, std::string() + kPathSep, base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + for (size_t i = 0; i < parts.size(); ++i) { + std::string part = parts[i]; + if (part != "." && part != "..") + part = sanitizePathComponent(part); + if (!result.empty() && result[result.length()-1] != kPathSep) + result += kPathSep; + result += part; + } + + return result; +} + +std::string joinPath(const std::string& s1, const std::string& s2) { + if (s1.empty() && s2.empty()) + return std::string(); + if (s1.empty()) + return s2; + if (s2.empty()) + return s1; + +#if defined(OS_WIN) + // Don't try to join absolute paths on Windows. + // Skip this check on POSIX where it's more difficult to differentiate. + if (isAbsolutePath(s2)) + return s2; +#endif + + std::string result = s1; + if (result[result.size() - 1] != kPathSep) + result += kPathSep; + if (s2[0] == kPathSep) + result += s2.substr(1); + else + result += s2; + return result; +} + +#if defined(OS_WIN) +// This will only be non-nullptr in the chrome_elf address space. +CefCrashReporterClient* g_crash_reporter_client = nullptr; +#endif + +} // namespace + +#if defined(OS_WIN) + +extern "C" { + +// Export functions from chrome_elf that are required by +// crash_reporting_win::InitializeCrashReportingForModule(). + +size_t __declspec(dllexport) __cdecl GetCrashKeyCountImpl() { + if (!g_crash_reporter_client) + return 0; + return g_crash_reporter_client->GetCrashKeyCount(); +} + +bool __declspec(dllexport) __cdecl GetCrashKeyImpl(size_t index, + const char** key_name, + size_t* max_length) { + if (!g_crash_reporter_client) + return false; + return g_crash_reporter_client->GetCrashKey(index, key_name, max_length); +} + +} // extern "C" + +#endif // OS_WIN CefCrashReporterClient::CefCrashReporterClient() {} CefCrashReporterClient::~CefCrashReporterClient() {} +// Be aware that logging is not initialized at the time this method is called. +bool CefCrashReporterClient::ReadCrashConfigFile() { + if (has_crash_config_file_) + return true; + + PathString config_path = GetCrashConfigPath(); + if (config_path.empty()) + return false; + #if defined(OS_WIN) + FILE* fp = _wfopen(config_path.c_str(), L"r"); +#else + FILE* fp = fopen(config_path.c_str(), "r"); +#endif + if (!fp) + return false; + + char line[1000]; + + enum section { + kNoSection, + kConfigSection, + kCrashKeysSection, + } current_section = kNoSection; + + while (fgets(line, sizeof(line) - 1, fp) != NULL) { + std::string str = line; + base::TrimString(str, base::kWhitespaceASCII, &str); + if (str.empty() || str[0] == '#') + continue; + + if (str == "[Config]") { + current_section = kConfigSection; + continue; + } else if (str == "[CrashKeys]") { + current_section = kCrashKeysSection; + continue; + } else if (str[0] == '[') { + current_section = kNoSection; + continue; + } + + if (current_section == kNoSection) + continue; + + size_t div = str.find('='); + if (div == std::string::npos) + continue; + + std::string name_str = str.substr(0, div); + base::TrimString(name_str, base::kWhitespaceASCII, &name_str); + std::string val_str = str.substr(div + 1); + base::TrimString(val_str, base::kWhitespaceASCII, &val_str); + if (name_str.empty() || val_str.empty()) + continue; + + if (current_section == kConfigSection) { + if (name_str == "ServerURL") { + if (val_str.find("http://") == 0 || val_str.find("https://") == 0) + server_url_ = val_str; + } else if (name_str == "RateLimitEnabled") { + rate_limit_ = (base::EqualsCaseInsensitiveASCII(val_str, "true") || + val_str == "1"); + } else if (name_str == "MaxUploadsPerDay") { + if (base::StringToInt(val_str, &max_uploads_)) { + if (max_uploads_ < 0) + max_uploads_ = 0; + } + } else if (name_str == "MaxDatabaseSizeInMb") { + if (base::StringToInt(val_str, &max_db_size_)) { + if (max_db_size_ < 0) + max_db_size_ = 0; + } + } else if (name_str == "MaxDatabaseAgeInDays") { + if (base::StringToInt(val_str, &max_db_age_)) { + if (max_db_age_ < 0) + max_db_age_ = 0; + } + } +#if defined(OS_WIN) + else if (name_str == "ExternalHandler") { + external_handler_ = sanitizePath(name_str); + } else if (name_str == "AppName") { + app_name_ = sanitizePathComponent(val_str); + } +#endif + } else if (current_section == kCrashKeysSection) { + size_t max_size = 0; + if (val_str == "small") + max_size = crash_keys::kSmallSize; + else if (val_str == "medium") + max_size = crash_keys::kMediumSize; + else if (val_str == "large") + max_size = crash_keys::kLargeSize; + + if (max_size == 0) + continue; + + crash_keys_.push_back({name_str, max_size}); + } + } + + fclose(fp); + + // Add the list of potential crash keys from chrome, content and other layers. + // Do it here so that they're also exported to the libcef module for Windows. + { + std::vector keys; + crash_keys::GetChromeCrashKeys(keys); + + if (!keys.empty()) { + crash_keys_.reserve(crash_keys_.size() + keys.size()); + for (const auto& key : keys) { + crash_keys_.push_back({key.key_name, key.max_length}); + } + } + } + + has_crash_config_file_ = true; + return true; +} + +bool CefCrashReporterClient::HasCrashConfigFile() const { + return has_crash_config_file_; +} + +#if defined(OS_WIN) + +// static +void CefCrashReporterClient::InitializeCrashReportingForProcess() { + if (g_crash_reporter_client) + return; + + g_crash_reporter_client = new CefCrashReporterClient(); + ANNOTATE_LEAKING_OBJECT_PTR(g_crash_reporter_client); + + if (!g_crash_reporter_client->ReadCrashConfigFile()) + return; + + std::string process_type = install_static::GetSwitchValueFromCommandLine( + ::GetCommandLineA(), install_static::kProcessType); + if (process_type != install_static::kCrashpadHandler) { + crash_reporter::SetCrashReporterClient(g_crash_reporter_client); + + // If |embedded_handler| is true then we launch another instance of the main + // executable as the crashpad-handler process. + const bool embedded_handler = + !g_crash_reporter_client->HasCrashExternalHandler(); + if (embedded_handler) { + crash_reporter::InitializeCrashpadWithEmbeddedHandler( + process_type.empty(), process_type); + } else { + crash_reporter::InitializeCrashpad(process_type.empty(), process_type); + } + } +} + +bool CefCrashReporterClient::GetAlternativeCrashDumpLocation( + base::string16* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + *crash_dir = + install_static::GetEnvironmentString16(L"BREAKPAD_DUMP_LOCATION"); + return !crash_dir->empty(); +} + void CefCrashReporterClient::GetProductNameAndVersion( const base::string16& exe_path, base::string16* product_name, @@ -32,9 +471,27 @@ void CefCrashReporterClient::GetProductNameAndVersion( *special_build = base::string16(); *channel_name = base::string16(); } -#endif -#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +bool CefCrashReporterClient::GetCrashDumpLocation(base::string16* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + if (GetAlternativeCrashDumpLocation(crash_dir)) + return true; + + return install_static::GetDefaultCrashDumpLocation( + crash_dir, base::UTF8ToUTF16(app_name_)); +} + +bool CefCrashReporterClient::GetCrashMetricsLocation( + base::string16* metrics_dir) { + return install_static::GetDefaultUserDataDirectory( + metrics_dir, base::UTF8ToUTF16(app_name_)); +} + +#elif defined(OS_POSIX) + +#if !defined(OS_MACOSX) + void CefCrashReporterClient::GetProductNameAndVersion( const char** product_name, const char** version) { @@ -45,27 +502,6 @@ void CefCrashReporterClient::GetProductNameAndVersion( base::FilePath CefCrashReporterClient::GetReporterLogFilename() { return base::FilePath(FILE_PATH_LITERAL("uploads.log")); } -#endif - -#if defined(OS_WIN) -bool CefCrashReporterClient::GetCrashDumpLocation(base::string16* crash_dir) { -#else -bool CefCrashReporterClient::GetCrashDumpLocation(base::FilePath* crash_dir) { -#endif - if (!base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kCrashDumpsDir)) - return false; - - base::FilePath crash_directory = - base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( - switches::kCrashDumpsDir); -#if defined(OS_WIN) - *crash_dir = crash_directory.value(); -#else - *crash_dir = std::move(crash_directory); -#endif - return true; -} bool CefCrashReporterClient::EnableBreakpadForProcess( const std::string& process_type) { @@ -74,3 +510,121 @@ bool CefCrashReporterClient::EnableBreakpadForProcess( process_type == switches::kZygoteProcess || process_type == switches::kGpuProcess; } + +#endif // !defined(OS_MACOSX) + +bool CefCrashReporterClient::GetCrashDumpLocation(base::FilePath* crash_dir) { + // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate + // location to write breakpad crash dumps can be set. + std::unique_ptr env(base::Environment::Create()); + std::string alternate_crash_dump_location; + if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { + base::FilePath crash_dumps_dir_path = + base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); + PathService::Override(chrome::DIR_CRASH_DUMPS, crash_dumps_dir_path); + } + return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir); +} + +#endif // !defined(OS_POSIX) + +bool CefCrashReporterClient::GetCollectStatsConsent() { + return true; +} + +bool CefCrashReporterClient::GetCollectStatsInSample() { + return true; +} + +#if defined(OS_WIN) || defined(OS_MACOSX) +bool CefCrashReporterClient::ReportingIsEnforcedByPolicy( + bool* crashpad_enabled) { + *crashpad_enabled = true; + return true; +} +#endif + +size_t CefCrashReporterClient::RegisterCrashKeys() { + std::vector keys; + + if (!crash_keys_.empty()) { + keys.reserve(crash_keys_.size()); + for (const auto& key : crash_keys_) { + keys.push_back({key.key_name_.c_str(), key.max_length_}); + } + } + + return base::debug::InitCrashKeys(&keys[0], keys.size(), + crash_keys::kChunkMaxLength); +} + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +bool CefCrashReporterClient::IsRunningUnattended() { + // Crash upload will only be enabled with Breakpad on Linux if this method + // returns false. + return false; +} +#endif + +#if defined(OS_WIN) +size_t CefCrashReporterClient::GetCrashKeyCount() const { + return crash_keys_.size(); +} + +bool CefCrashReporterClient::GetCrashKey(size_t index, + const char** key_name, + size_t* max_length) const { + if (index >= crash_keys_.size()) + return false; + + const auto& key = crash_keys_[index]; + *key_name = key.key_name_.c_str(); + *max_length = key.max_length_; + return true; +} +#endif // defined(OS_WIN) + +std::string CefCrashReporterClient::GetCrashServerURL() { + return server_url_; +} + +// See HandlerMain() in third_party/crashpad/crashpad/handler/handler_main.cc +// for supported arguments. +void CefCrashReporterClient::GetCrashOptionalArguments( + std::vector* arguments) { + if (!rate_limit_) + arguments->push_back(std::string("--no-rate-limit")); + + if (max_uploads_ > 0) { + arguments->push_back( + std::string("--max-uploads=") + base::IntToString(max_uploads_)); + } + + if (max_db_size_ > 0) { + arguments->push_back( + std::string("--max-db-size=") + base::IntToString(max_db_size_)); + } + + if (max_db_age_ > 0) { + arguments->push_back( + std::string("--max-db-age=") + base::IntToString(max_db_age_)); + } +} + +#if defined(OS_WIN) + +base::string16 CefCrashReporterClient::GetCrashExternalHandler( + const base::string16& exe_dir) { + if (external_handler_.empty()) + return CrashReporterClient::GetCrashExternalHandler(exe_dir); + if (isAbsolutePath(external_handler_)) + return base::UTF8ToUTF16(external_handler_); + return base::UTF8ToWide( + joinPath(base::UTF16ToUTF8(exe_dir), external_handler_)); +} + +bool CefCrashReporterClient::HasCrashExternalHandler() const { + return !external_handler_.empty(); +} + +#endif // defined(OS_WIN) diff --git a/libcef/common/crash_reporter_client.h b/libcef/common/crash_reporter_client.h index 3dd2dbf61..d8dc1f4a2 100644 --- a/libcef/common/crash_reporter_client.h +++ b/libcef/common/crash_reporter_client.h @@ -1,49 +1,109 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2016 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. #ifndef CEF_LIBCEF_COMMON_CRASH_REPORTER_CLIENT_H_ #define CEF_LIBCEF_COMMON_CRASH_REPORTER_CLIENT_H_ +#include +#include + #include "base/macros.h" #include "build/build_config.h" #include "components/crash/content/app/crash_reporter_client.h" +// Global object that is instantiated in each process and configures crash +// reporting. On Windows this is created by the +// InitializeCrashReportingForProcess() method called from chrome_elf. On +// Linux and macOS this is created by crash_reporting::BasicStartupComplete(). class CefCrashReporterClient : public crash_reporter::CrashReporterClient { public: CefCrashReporterClient(); ~CefCrashReporterClient() override; + // Reads the crash config file and returns true on success. Failure to read + // the crash config file will disable crash reporting. This method should be + // called immediately after the CefCrashReporterClient instance is created. + bool ReadCrashConfigFile(); + bool HasCrashConfigFile() const; + #if defined(OS_WIN) - // Returns a textual description of the product type and version to include - // in the crash report. + // Called from chrome_elf (chrome_elf/crash/crash_helper.cc) to instantiate + // a process wide instance of CefCrashReporterClient and initialize crash + // reporting for the process. The instance is leaked. + // crash_reporting_win::InitializeCrashReportingForModule() will be called + // later from crash_reporting::PreSandboxStartup() to read global state into + // the module address space. + static void InitializeCrashReportingForProcess(); + + bool GetAlternativeCrashDumpLocation(base::string16* crash_dir) override; void GetProductNameAndVersion(const base::string16& exe_path, base::string16* product_name, base::string16* version, base::string16* special_build, base::string16* channel_name) override; -#endif - -#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) - // Returns a textual description of the product type and version to include - // in the crash report. + bool GetCrashDumpLocation(base::string16* crash_dir) override; + bool GetCrashMetricsLocation(base::string16* metrics_dir) override; +#elif defined(OS_POSIX) +#if !defined(OS_MACOSX) void GetProductNameAndVersion(const char** product_name, const char** version) override; - base::FilePath GetReporterLogFilename() override; -#endif - - // The location where minidump files should be written. Returns true if - // |crash_dir| was set. -#if defined(OS_WIN) - bool GetCrashDumpLocation(base::string16* crash_dir) override; -#else - bool GetCrashDumpLocation(base::FilePath* crash_dir) override; -#endif - bool EnableBreakpadForProcess(const std::string& process_type) override; +#endif + bool GetCrashDumpLocation(base::FilePath* crash_dir) override; +#endif // defined(OS_POSIX) + + // All of these methods must return true to enable crash report upload. + bool GetCollectStatsConsent() override; + bool GetCollectStatsInSample() override; +#if defined(OS_WIN) || defined(OS_MACOSX) + bool ReportingIsEnforcedByPolicy(bool* crashpad_enabled) override; +#endif + + size_t RegisterCrashKeys() override; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + bool IsRunningUnattended() override; +#endif + +#if defined(OS_WIN) + size_t GetCrashKeyCount() const; + bool GetCrashKey(size_t index, + const char** key_name, + size_t* max_length) const; +#endif + + std::string GetCrashServerURL() override; + void GetCrashOptionalArguments(std::vector* arguments) override; + +#if defined(OS_WIN) + base::string16 GetCrashExternalHandler( + const base::string16& exe_dir) override; + bool HasCrashExternalHandler() const; +#endif private: + bool has_crash_config_file_ = false; + + // Values that will persist until the end of the program. + // Matches the members of base::debug::CrashKey. + struct StoredCrashKey { + std::string key_name_; + size_t max_length_; + }; + std::vector crash_keys_; + std::string server_url_; + bool rate_limit_ = true; + int max_uploads_ = 5; + int max_db_size_ = 20; + int max_db_age_ = 5; + +#if defined(OS_WIN) + std::string app_name_ = "CEF"; + std::string external_handler_; +#endif + DISALLOW_COPY_AND_ASSIGN(CefCrashReporterClient); }; diff --git a/libcef/common/crash_reporting.cc b/libcef/common/crash_reporting.cc new file mode 100644 index 000000000..e2e9f34ec --- /dev/null +++ b/libcef/common/crash_reporting.cc @@ -0,0 +1,190 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2016 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#include "libcef/common/crash_reporting.h" + +#include "include/cef_crash_util.h" +#include "libcef/common/cef_switches.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/debug/crash_logging.h" +#include "base/strings/string_util.h" +#include "chrome/common/crash_keys.h" +#include "content/public/common/content_switches.h" + +#if defined(OS_MACOSX) +#include "base/mac/foundation_util.h" +#include "components/crash/content/app/crashpad.h" +#include "components/crash/core/common/crash_keys.h" +#include "content/public/common/content_paths.h" +#endif + +#if defined(OS_POSIX) +#include "base/lazy_instance.h" +#include "libcef/common/crash_reporter_client.h" +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +#include "components/crash/content/app/breakpad_linux.h" +#endif + +#if defined(OS_WIN) +#include "libcef/common/crash_reporting_win.h" +#endif + +namespace crash_reporting { + +namespace { + +bool g_crash_reporting_enabled = false; + +#if defined(OS_POSIX) +base::LazyInstance::Leaky g_crash_reporter_client = + LAZY_INSTANCE_INITIALIZER; + +void InitCrashReporter(const base::CommandLine& command_line, + const std::string& process_type) { + CefCrashReporterClient* crash_client = g_crash_reporter_client.Pointer(); + if (!crash_client->HasCrashConfigFile()) + return; + + crash_reporter::SetCrashReporterClient(crash_client); + +#if defined(OS_MACOSX) + // TODO(mark): Right now, InitializeCrashpad() needs to be called after + // CommandLine::Init() and configuration of chrome::DIR_CRASH_DUMPS. Ideally, + // Crashpad initialization could occur sooner, preferably even before the + // framework dylib is even loaded, to catch potential early crashes. + crash_reporter::InitializeCrashpad(process_type.empty(), process_type); + + // Mac Chrome is packaged with a main app bundle and a helper app bundle. + // The main app bundle should only be used for the browser process, so it + // should never see a --type switch (switches::kProcessType). Likewise, + // the helper should always have a --type switch. + // + // This check is done this late so there is already a call to + // base::mac::IsBackgroundOnlyProcess(), so there is no change in + // startup/initialization order. + + // The helper's Info.plist marks it as a background only app. + if (base::mac::IsBackgroundOnlyProcess()) { + CHECK(command_line.HasSwitch(switches::kProcessType) && + !process_type.empty()) + << "Helper application requires --type."; + } else { + CHECK(!command_line.HasSwitch(switches::kProcessType) && + process_type.empty()) + << "Main application forbids --type, saw " << process_type; + } + + g_crash_reporting_enabled = true; +#else // !defined(OS_MACOSX) + breakpad::SetCrashServerURL(crash_client->GetCrashServerURL()); + + if (process_type != switches::kZygoteProcess) { + // Crash reporting for subprocesses created using the zygote will be + // initialized in ZygoteForked. + breakpad::InitCrashReporter(process_type); + + g_crash_reporting_enabled = true; + } +#endif // !defined(OS_MACOSX) +} +#endif // defined(OS_POSIX) + +// Used to exclude command-line flags from crash reporting. +bool IsBoringCEFSwitch(const std::string& flag) { + if (crash_keys::IsBoringChromeSwitch(flag)) + return true; + + static const char* const kIgnoreSwitches[] = { + // CEF internals. + switches::kLogFile, + + // Chromium internals. + "content-image-texture-target", + "mojo-platform-channel-handle", + "primordial-pipe-token", + "service-request-channel-token", + }; + + if (!base::StartsWith(flag, "--", base::CompareCase::SENSITIVE)) + return false; + + size_t end = flag.find("="); + size_t len = (end == std::string::npos) ? flag.length() - 2 : end - 2; + for (size_t i = 0; i < arraysize(kIgnoreSwitches); ++i) { + if (flag.compare(2, len, kIgnoreSwitches[i]) == 0) + return true; + } + return false; +} + +} // namespace + +#if defined(OS_POSIX) +// Be aware that logging is not initialized at the time this method is called. +void BasicStartupComplete(base::CommandLine* command_line) { + CefCrashReporterClient* crash_client = g_crash_reporter_client.Pointer(); + if (crash_client->ReadCrashConfigFile()) { +#if !defined(OS_MACOSX) + // Breakpad requires this switch. + command_line->AppendSwitch(switches::kEnableCrashReporter); +#endif + } +} +#endif + +void PreSandboxStartup(const base::CommandLine& command_line, + const std::string& process_type) { +#if defined(OS_POSIX) + // Initialize crash reporting here on macOS and Linux. Crash reporting on + // Windows is initialized from context.cc. + InitCrashReporter(command_line, process_type); +#elif defined(OS_WIN) + // Initialize crash key globals in the module (libcef) address space. + g_crash_reporting_enabled = + crash_reporting_win::InitializeCrashReportingForModule(); +#endif + + if (g_crash_reporting_enabled) { + LOG(INFO) << "Crash reporting enabled for process: " << + (process_type.empty() ? "browser" : process_type.c_str()); + } + + // After platform crash reporting have been initialized, store the command + // line for crash reporting. + crash_keys::SetSwitchesFromCommandLine(command_line, &IsBoringCEFSwitch); +} + +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_MACOSX) +void ZygoteForked(base::CommandLine* command_line, + const std::string& process_type) { + CefCrashReporterClient* crash_client = g_crash_reporter_client.Pointer(); + if (crash_client->HasCrashConfigFile()) { + // Breakpad requires this switch. + command_line->AppendSwitch(switches::kEnableCrashReporter); + } + + InitCrashReporter(*command_line, process_type); + + if (g_crash_reporting_enabled) { + LOG(INFO) << "Crash reporting enabled for process: " << process_type; + } + + // Reset the command line for the newly spawned process. + crash_keys::SetSwitchesFromCommandLine(*command_line, &IsBoringCEFSwitch); +} +#endif + +} // namespace crash_reporting + +bool CefCrashReportingEnabled() { + return crash_reporting::g_crash_reporting_enabled; +} + +void CefSetCrashKeyValue(const CefString& key, const CefString& value) { + base::debug::SetCrashKeyValue(key.ToString(), value.ToString()); +} diff --git a/libcef/common/crash_reporting.h b/libcef/common/crash_reporting.h new file mode 100644 index 000000000..1e96b950f --- /dev/null +++ b/libcef/common/crash_reporting.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2016 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#include + +#include "build/build_config.h" + +namespace base { +class CommandLine; +} + +namespace crash_reporting { + +// Functions are called from similarly named methods in CefMainDelegate. + +#if defined(OS_POSIX) +void BasicStartupComplete(base::CommandLine* command_line); +#endif + +void PreSandboxStartup(const base::CommandLine& command_line, + const std::string& process_type); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_MACOSX) +void ZygoteForked(base::CommandLine* command_line, + const std::string& process_type); +#endif + +} // namespace crash_reporting diff --git a/libcef/common/crash_reporting_win.cc b/libcef/common/crash_reporting_win.cc new file mode 100644 index 000000000..aa74c76ef --- /dev/null +++ b/libcef/common/crash_reporting_win.cc @@ -0,0 +1,120 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2016 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +#include + +#include "libcef/common/crash_reporting_win.h" + +#include "base/debug/crash_logging.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/common/chrome_constants.h" +#include "components/crash/core/common/crash_keys.h" + +namespace crash_reporting_win { + +namespace { + +// exported in crash_reporter_client.cc: +// size_t __declspec(dllexport) __cdecl GetCrashKeyCountImpl. +typedef size_t(__cdecl* GetCrashKeyCount)(); + +// exported in crash_reporter_client.cc: +// bool __declspec(dllexport) __cdecl GetCrashKeyImpl. +typedef bool(__cdecl* GetCrashKey)(size_t, const char**, size_t*); + +size_t GetCrashKeyCountTrampoline() { + static GetCrashKeyCount get_crash_key_count = []() { + HMODULE elf_module = GetModuleHandle(chrome::kChromeElfDllName); + return reinterpret_cast( + elf_module ? GetProcAddress(elf_module, "GetCrashKeyCountImpl") + : nullptr); + }(); + if (get_crash_key_count) { + return (get_crash_key_count)(); + } + return 0; +} + +bool GetCrashKeyTrampoline(size_t index, + const char** key_name, + size_t* max_length) { + static GetCrashKey get_crash_key = []() { + HMODULE elf_module = GetModuleHandle(chrome::kChromeElfDllName); + return reinterpret_cast( + elf_module ? GetProcAddress(elf_module, "GetCrashKeyImpl") + : nullptr); + }(); + if (get_crash_key) { + return (get_crash_key)(index, key_name, max_length); + } + return false; +} + + +// From chrome/common/child_process_logging_win.cc: + +// exported in breakpad_win.cc/crashpad_win.cc: +// void __declspec(dllexport) __cdecl SetCrashKeyValueImpl. +typedef void(__cdecl* SetCrashKeyValue)(const wchar_t*, const wchar_t*); + +// exported in breakpad_win.cc/crashpad_win.cc: +// void __declspec(dllexport) __cdecl ClearCrashKeyValueImpl. +typedef void(__cdecl* ClearCrashKeyValue)(const wchar_t*); + +void SetCrashKeyValueTrampoline(const base::StringPiece& key, + const base::StringPiece& value) { + static SetCrashKeyValue set_crash_key = []() { + HMODULE elf_module = GetModuleHandle(chrome::kChromeElfDllName); + return reinterpret_cast( + elf_module ? GetProcAddress(elf_module, "SetCrashKeyValueImpl") + : nullptr); + }(); + if (set_crash_key) { + (set_crash_key)(base::UTF8ToWide(key).data(), + base::UTF8ToWide(value).data()); + } +} + +void ClearCrashKeyValueTrampoline(const base::StringPiece& key) { + static ClearCrashKeyValue clear_crash_key = []() { + HMODULE elf_module = GetModuleHandle(chrome::kChromeElfDllName); + return reinterpret_cast( + elf_module ? GetProcAddress(elf_module, "ClearCrashKeyValueImpl") + : nullptr); + }(); + if (clear_crash_key) + (clear_crash_key)(base::UTF8ToWide(key).data()); +} + +} // namespace + +bool InitializeCrashReportingForModule() { + base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueTrampoline, + &ClearCrashKeyValueTrampoline); + + std::vector keys; + + size_t key_ct = GetCrashKeyCountTrampoline(); + if (key_ct > 0U) { + keys.reserve(key_ct); + + const char* key_name; + size_t max_length; + + for (size_t i = 0; i < key_ct; ++i) { + if (GetCrashKeyTrampoline(i, &key_name, &max_length)) + keys.push_back({key_name, max_length}); + } + } + + if (!keys.empty()) { + base::debug::InitCrashKeys(&keys[0], keys.size(), + crash_keys::kChunkMaxLength); + return true; + } + + return false; +} + +} // namespace crash_reporting_win diff --git a/libcef/common/crash_reporting_win.h b/libcef/common/crash_reporting_win.h new file mode 100644 index 000000000..28c43dad8 --- /dev/null +++ b/libcef/common/crash_reporting_win.h @@ -0,0 +1,11 @@ +// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright +// 2016 The Chromium Authors. All rights reserved. Use of this source code is +// governed by a BSD-style license that can be found in the LICENSE file. + +namespace crash_reporting_win { + +// Called from libcef to initialize crash key globals. Retrieves the necessary +// state from chrome_elf via exported functions. +bool InitializeCrashReportingForModule(); + +} // namespace crash_reporting_win diff --git a/libcef/common/main_delegate.cc b/libcef/common/main_delegate.cc index 657298fb8..2b7cb06e2 100644 --- a/libcef/common/main_delegate.cc +++ b/libcef/common/main_delegate.cc @@ -7,7 +7,7 @@ #include "libcef/browser/context.h" #include "libcef/common/cef_switches.h" #include "libcef/common/command_line_impl.h" -#include "libcef/common/crash_reporter_client.h" +#include "libcef/common/crash_reporting.h" #include "libcef/common/extensions/extensions_util.h" #include "libcef/renderer/content_renderer_client.h" #include "libcef/utility/content_utility_client.h" @@ -16,7 +16,6 @@ #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" -#include "base/lazy_instance.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" @@ -57,18 +56,11 @@ #if defined(OS_MACOSX) #include "libcef/common/util_mac.h" -#include "base/mac/os_crash_dumps.h" #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" -#include "components/crash/content/app/crashpad.h" -#include "components/crash/core/common/crash_keys.h" #include "content/public/common/content_paths.h" #endif -#if defined(OS_POSIX) && !defined(OS_MACOSX) -#include "components/crash/content/app/breakpad_linux.h" -#endif - #if defined(OS_LINUX) #include "base/environment.h" #include "base/nix/xdg_util.h" @@ -76,11 +68,6 @@ namespace { -#if defined(OS_POSIX) -base::LazyInstance::Leaky g_crash_reporter_client = - LAZY_INSTANCE_INITIALIZER; -#endif - #if defined(OS_MACOSX) base::FilePath GetFrameworksPath() { @@ -327,6 +314,12 @@ bool CefMainDelegate::BasicStartupComplete(int* exit_code) { std::string process_type = command_line->GetSwitchValueASCII(switches::kProcessType); +#if defined(OS_POSIX) + // Read the crash configuration file. Platforms using Breakpad also add a + // command-line switch. On Windows this is done from chrome_elf. + crash_reporting::BasicStartupComplete(command_line); +#endif + if (process_type.empty()) { // In the browser process. Populate the global command-line object. const CefSettings& settings = CefContext::Get()->settings(); @@ -524,18 +517,6 @@ void CefMainDelegate::PreSandboxStartup() { const std::string& process_type = command_line->GetSwitchValueASCII(switches::kProcessType); -#if defined(OS_POSIX) - if (command_line->HasSwitch(switches::kEnableCrashReporter)) { - crash_reporter::SetCrashReporterClient(g_crash_reporter_client.Pointer()); -#if defined(OS_MACOSX) - InitMacCrashReporter(*command_line, process_type); -#else - if (process_type != switches::kZygoteProcess) - breakpad::InitCrashReporter(process_type); -#endif - } -#endif // defined(OS_POSIX) - if (process_type.empty()) { // Only override these paths when executing the main process. #if defined(OS_MACOSX) @@ -544,9 +525,13 @@ void CefMainDelegate::PreSandboxStartup() { OverridePepperFlashSystemPluginPath(); - // Paths used to locate spell checking dictionary files. const base::FilePath& user_data_path = GetUserDataPath(); PathService::Override(chrome::DIR_USER_DATA, user_data_path); + + // Path used for crash dumps. + PathService::Override(chrome::DIR_CRASH_DUMPS, user_data_path); + + // Path used for spell checking dictionary files. PathService::OverrideAndCreateIfNeeded( chrome::DIR_APP_DICTIONARIES, user_data_path.AppendASCII("Dictionaries"), @@ -557,6 +542,10 @@ void CefMainDelegate::PreSandboxStartup() { if (command_line->HasSwitch(switches::kDisablePackLoading)) content_client_.set_pack_loading_disabled(true); + // Initialize crash reporting state for this process/module. + // chrome::DIR_CRASH_DUMPS must be configured before calling this function. + crash_reporting::PreSandboxStartup(*command_line, process_type); + InitializeResourceBundle(); chrome::InitializePDF(); } @@ -609,13 +598,12 @@ void CefMainDelegate::ProcessExiting(const std::string& process_type) { #if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_MACOSX) void CefMainDelegate::ZygoteForked() { - const base::CommandLine* command_line = + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (command_line->HasSwitch(switches::kEnableCrashReporter)) { - const std::string& process_type = command_line->GetSwitchValueASCII( + const std::string& process_type = command_line->GetSwitchValueASCII( switches::kProcessType); - breakpad::InitCrashReporter(process_type); - } + // Initialize crash reporting state for the newly forked process. + crash_reporting::ZygoteForked(command_line, process_type); } #endif @@ -757,51 +745,3 @@ void CefMainDelegate::InitializeResourceBundle() { content_client_.set_allow_pack_file_load(false); } } - -#if defined(OS_MACOSX) -// Based on ChromeMainDelegate::InitMacCrashReporter. -void CefMainDelegate::InitMacCrashReporter( - const base::CommandLine& command_line, - const std::string& process_type) { - // TODO(mark): Right now, InitializeCrashpad() needs to be called after - // CommandLine::Init() and chrome::RegisterPathProvider(). Ideally, Crashpad - // initialization could occur sooner, preferably even before the framework - // dylib is even loaded, to catch potential early crashes. - - const bool browser_process = process_type.empty(); - const bool install_from_dmg_relauncher_process = - process_type == switches::kRelauncherProcess && - command_line.HasSwitch(switches::kRelauncherProcessDMGDevice); - - const bool initial_client = - browser_process || install_from_dmg_relauncher_process; - - crash_reporter::InitializeCrashpad(initial_client, process_type); - - if (!browser_process) { - std::string metrics_client_id = - command_line.GetSwitchValueASCII(switches::kMetricsClientID); - crash_keys::SetMetricsClientIdFromGUID(metrics_client_id); - } - - // Mac Chrome is packaged with a main app bundle and a helper app bundle. - // The main app bundle should only be used for the browser process, so it - // should never see a --type switch (switches::kProcessType). Likewise, - // the helper should always have a --type switch. - // - // This check is done this late so there is already a call to - // base::mac::IsBackgroundOnlyProcess(), so there is no change in - // startup/initialization order. - - // The helper's Info.plist marks it as a background only app. - if (base::mac::IsBackgroundOnlyProcess()) { - CHECK(command_line.HasSwitch(switches::kProcessType) && - !process_type.empty()) - << "Helper application requires --type."; - } else { - CHECK(!command_line.HasSwitch(switches::kProcessType) && - process_type.empty()) - << "Main application forbids --type, saw " << process_type; - } -} -#endif // defined(OS_MACOSX) \ No newline at end of file diff --git a/libcef/common/main_delegate.h b/libcef/common/main_delegate.h index 7722b7cf8..6607470ee 100644 --- a/libcef/common/main_delegate.h +++ b/libcef/common/main_delegate.h @@ -56,11 +56,6 @@ class CefMainDelegate : public content::ContentMainDelegate { private: void InitializeResourceBundle(); -#if defined(OS_MACOSX) - void InitMacCrashReporter(const base::CommandLine& command_line, - const std::string& process_type); -#endif // defined(OS_MACOSX) - std::unique_ptr browser_runner_; std::unique_ptr ui_thread_; diff --git a/libcef/features/BUILD.gn b/libcef/features/BUILD.gn new file mode 100644 index 000000000..bdedff82c --- /dev/null +++ b/libcef/features/BUILD.gn @@ -0,0 +1,19 @@ +# Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/buildflag_header.gni") +import("//cef/libcef/features/features.gni") + +# This file is in a separate directory so all targets in the build can refer to +# the buildflag header to get the necessary preprocessor defines without +# bringing in any CEF targets. Other targets can depend on this target +# regardless of whether CEF is being built. + +buildflag_header("features") { + header = "features.h" + + flags = [ + "ENABLE_CEF=$enable_cef", + ] +} diff --git a/libcef/features/features.gni b/libcef/features/features.gni new file mode 100644 index 000000000..67866d4ee --- /dev/null +++ b/libcef/features/features.gni @@ -0,0 +1,7 @@ +# Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +declare_args() { + enable_cef = true +} diff --git a/libcef_dll/libcef_dll.cc b/libcef_dll/libcef_dll.cc index ad9fc37c3..cc1aafe56 100644 --- a/libcef_dll/libcef_dll.cc +++ b/libcef_dll/libcef_dll.cc @@ -12,6 +12,8 @@ #include "include/cef_app.h" #include "include/capi/cef_app_capi.h" +#include "include/cef_crash_util.h" +#include "include/capi/cef_crash_util_capi.h" #include "include/cef_file_util.h" #include "include/capi/cef_file_util_capi.h" #include "include/cef_geolocation.h" @@ -391,6 +393,35 @@ CEF_EXPORT void cef_enable_highdpi_support() { CefEnableHighDPISupport(); } +CEF_EXPORT int cef_crash_reporting_enabled() { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + bool _retval = CefCrashReportingEnabled(); + + // Return type: bool + return _retval; +} + +CEF_EXPORT void cef_set_crash_key_value(const cef_string_t* key, + const cef_string_t* value) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: key; type: string_byref_const + DCHECK(key); + if (!key) + return; + // Verify param: value; type: string_byref_const + DCHECK(value); + if (!value) + return; + + // Execute + CefSetCrashKeyValue( + CefString(key), + CefString(value)); +} + CEF_EXPORT int cef_create_directory(const cef_string_t* full_path) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING diff --git a/libcef_dll/wrapper/libcef_dll_wrapper.cc b/libcef_dll/wrapper/libcef_dll_wrapper.cc index 092a372ff..08ddbd830 100644 --- a/libcef_dll/wrapper/libcef_dll_wrapper.cc +++ b/libcef_dll/wrapper/libcef_dll_wrapper.cc @@ -12,6 +12,8 @@ #include "include/cef_app.h" #include "include/capi/cef_app_capi.h" +#include "include/cef_crash_util.h" +#include "include/capi/cef_crash_util_capi.h" #include "include/cef_file_util.h" #include "include/capi/cef_file_util_capi.h" #include "include/cef_geolocation.h" @@ -383,6 +385,35 @@ CEF_GLOBAL void CefEnableHighDPISupport() { cef_enable_highdpi_support(); } +CEF_GLOBAL bool CefCrashReportingEnabled() { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + int _retval = cef_crash_reporting_enabled(); + + // Return type: bool + return _retval?true:false; +} + +CEF_GLOBAL void CefSetCrashKeyValue(const CefString& key, + const CefString& value) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: key; type: string_byref_const + DCHECK(!key.empty()); + if (key.empty()) + return; + // Verify param: value; type: string_byref_const + DCHECK(!value.empty()); + if (value.empty()) + return; + + // Execute + cef_set_crash_key_value( + key.GetStruct(), + value.GetStruct()); +} + CEF_GLOBAL bool CefCreateDirectory(const CefString& full_path) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING diff --git a/patch/patch.cfg b/patch/patch.cfg index c959ab68a..95b36b0b9 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -294,4 +294,19 @@ patches = [ 'name': 'render_widget_latency_2060', 'path': '../', }, + { + # Implement breakpad/crashpad customization required by CEF. + # https://bitbucket.org/chromiumembedded/cef/issues/1995 + 'name': 'crashpad_1995', + 'path': '../', + }, + { + # Support customization of crash report pruning limits. + # https://bugs.chromium.org/p/crashpad/issues/detail?id=142 + # + # Implement better rate-limiting/retry logic. + # https://bugs.chromium.org/p/crashpad/issues/detail?id=23 + 'name': 'crashpad_tp_1995', + 'path': '../third_party/crashpad/', + }, ] diff --git a/patch/patches/crashpad_1995.patch b/patch/patches/crashpad_1995.patch new file mode 100644 index 000000000..53b12876c --- /dev/null +++ b/patch/patches/crashpad_1995.patch @@ -0,0 +1,509 @@ +diff --git build/secondary/third_party/crashpad/crashpad/handler/BUILD.gn build/secondary/third_party/crashpad/crashpad/handler/BUILD.gn +index 6cd8b9f..dfbbdc6 100644 +--- build/secondary/third_party/crashpad/crashpad/handler/BUILD.gn ++++ build/secondary/third_party/crashpad/crashpad/handler/BUILD.gn +@@ -2,6 +2,8 @@ + # Use of this source code is governed by a BSD-style license that can be + # found in the LICENSE file. + ++import("//cef/libcef/features/features.gni") ++ + static_library("handler_lib") { + sources = [ + "crash_report_upload_thread.cc", +@@ -26,8 +28,18 @@ static_library("handler_lib") { + "../snapshot", + "../tools:tool_support", + "//base", ++ "//cef/libcef/features", + ] + ++ if (enable_cef) { ++ sources += [ ++ "//cef/libcef/common/cef_crash_report_upload_thread.cc", ++ "//cef/libcef/common/cef_crash_report_upload_thread.h", ++ ] ++ ++ include_dirs += [ "//cef" ] ++ } ++ + if (is_win) { + cflags = [ "/wd4201" ] + } +diff --git chrome/common/crash_keys.cc chrome/common/crash_keys.cc +index 800f704..e4b4063 100644 +--- chrome/common/crash_keys.cc ++++ chrome/common/crash_keys.cc +@@ -4,6 +4,8 @@ + + #include "chrome/common/crash_keys.h" + ++#include ++ + #include "base/base_switches.h" + #include "base/command_line.h" + #include "base/format_macros.h" +@@ -94,7 +96,7 @@ const char kViewCount[] = "view-count"; + + const char kZeroEncodeDetails[] = "zero-encode-details"; + +-size_t RegisterChromeCrashKeys() { ++void GetChromeCrashKeys(std::vector& keys) { + // The following keys may be chunked by the underlying crash logging system, + // but ultimately constitute a single key-value pair. + // +@@ -250,10 +252,16 @@ size_t RegisterChromeCrashKeys() { + + // This dynamic set of keys is used for sets of key value pairs when gathering + // a collection of data, like command line switches or extension IDs. +- std::vector keys( +- fixed_keys, fixed_keys + arraysize(fixed_keys)); ++ keys.reserve(keys.size() + arraysize(fixed_keys)); ++ std::copy(fixed_keys, fixed_keys + arraysize(fixed_keys), ++ std::back_inserter(keys)); + + crash_keys::GetCrashKeysForCommandLineSwitches(&keys); ++} ++ ++size_t RegisterChromeCrashKeys() { ++ std::vector keys; ++ GetChromeCrashKeys(keys); + + // Register the extension IDs. + { +@@ -287,7 +295,7 @@ size_t RegisterChromeCrashKeys() { + return base::debug::InitCrashKeys(&keys.at(0), keys.size(), kChunkMaxLength); + } + +-static bool IsBoringSwitch(const std::string& flag) { ++bool IsBoringChromeSwitch(const std::string& flag) { + static const char* const kIgnoreSwitches[] = { + switches::kEnableLogging, + switches::kFlagSwitchesBegin, +@@ -343,7 +351,7 @@ static bool IsBoringSwitch(const std::string& flag) { + } + + void SetCrashKeysFromCommandLine(const base::CommandLine& command_line) { +- return SetSwitchesFromCommandLine(command_line, &IsBoringSwitch); ++ return SetSwitchesFromCommandLine(command_line, &IsBoringChromeSwitch); + } + + void SetActiveExtensions(const std::set& extensions) { +diff --git chrome/common/crash_keys.h chrome/common/crash_keys.h +index 6f66031..1abcdf8 100644 +--- chrome/common/crash_keys.h ++++ chrome/common/crash_keys.h +@@ -23,10 +23,18 @@ class CommandLine; + + namespace crash_keys { + ++// Returns the list of potential crash keys that can be sent to the crash ++// server. ++void GetChromeCrashKeys(std::vector& keys); ++ + // Registers all of the potential crash keys that can be sent to the crash + // reporting server. Returns the size of the union of all keys. + size_t RegisterChromeCrashKeys(); + ++// Returns true if the specified command-line flag should be excluded from ++// crash reporting. ++bool IsBoringChromeSwitch(const std::string& flag); ++ + // Sets the kNumSwitches key and the set of keys named using kSwitchFormat based + // on the given |command_line|. + void SetCrashKeysFromCommandLine(const base::CommandLine& command_line); +diff --git chrome/install_static/install_util.cc chrome/install_static/install_util.cc +index edec76d..1db1c9c 100644 +--- chrome/install_static/install_util.cc ++++ chrome/install_static/install_util.cc +@@ -473,7 +473,9 @@ bool IsNonBrowserProcess() { + return g_process_type == ProcessType::NON_BROWSER_PROCESS; + } + +-bool GetDefaultUserDataDirectory(std::wstring* result) { ++bool GetDefaultUserDataDirectory( ++ std::wstring* result, ++ const std::wstring& install_sub_directory) { + // This environment variable should be set on Windows Vista and later + // (https://msdn.microsoft.com/library/windows/desktop/dd378457.aspx). + std::wstring user_data_dir = GetEnvironmentString16(L"LOCALAPPDATA"); +@@ -493,17 +495,23 @@ bool GetDefaultUserDataDirectory(std::wstring* result) { + result->swap(user_data_dir); + if ((*result)[result->length() - 1] != L'\\') + result->push_back(L'\\'); +- AppendChromeInstallSubDirectory(result, true /* include_suffix */); ++ if (!install_sub_directory.empty()) { ++ result->append(install_sub_directory); ++ } else { ++ AppendChromeInstallSubDirectory(result, true /* include_suffix */); ++ } + result->push_back(L'\\'); + result->append(kUserDataDirname); + return true; + } + +-bool GetDefaultCrashDumpLocation(std::wstring* crash_dir) { ++bool GetDefaultCrashDumpLocation( ++ std::wstring* crash_dir, ++ const std::wstring& install_sub_directory) { + // In order to be able to start crash handling very early, we do not rely on + // chrome's PathService entries (for DIR_CRASH_DUMPS) being available on + // Windows. See https://crbug.com/564398. +- if (!GetDefaultUserDataDirectory(crash_dir)) ++ if (!GetDefaultUserDataDirectory(crash_dir, install_sub_directory)) + return false; + + // We have to make sure the user data dir exists on first run. See +diff --git chrome/install_static/install_util.h chrome/install_static/install_util.h +index 4ded522..81eba43 100644 +--- chrome/install_static/install_util.h ++++ chrome/install_static/install_util.h +@@ -86,14 +86,18 @@ bool IsNonBrowserProcess(); + // TODO(ananta) + // http://crbug.com/604923 + // Unify this with the Browser Distribution code. +-bool GetDefaultUserDataDirectory(std::wstring* result); ++bool GetDefaultUserDataDirectory( ++ std::wstring* result, ++ const std::wstring& install_sub_directory = std::wstring()); + + // Populates |crash_dir| with the default crash dump location regardless of + // whether DIR_USER_DATA or DIR_CRASH_DUMPS has been overridden. + // TODO(ananta) + // http://crbug.com/604923 + // Unify this with the Browser Distribution code. +-bool GetDefaultCrashDumpLocation(std::wstring* crash_dir); ++bool GetDefaultCrashDumpLocation( ++ std::wstring* crash_dir, ++ const std::wstring& install_sub_directory = std::wstring()); + + // Returns the contents of the specified |variable_name| from the environment + // block of the calling process. Returns an empty string if the variable does +diff --git chrome_elf/BUILD.gn chrome_elf/BUILD.gn +index 0629b6f..9c150b5 100644 +--- chrome_elf/BUILD.gn ++++ chrome_elf/BUILD.gn +@@ -7,6 +7,7 @@ + + import("//build/config/compiler/compiler.gni") + import("//build/config/win/manifest.gni") ++import("//cef/libcef/features/features.gni") + import("//chrome/process_version_rc_template.gni") + import("//testing/test.gni") + +@@ -138,16 +139,40 @@ static_library("blacklist") { + + static_library("crash") { + sources = [ +- "../chrome/app/chrome_crash_reporter_client_win.cc", +- "../chrome/app/chrome_crash_reporter_client_win.h", +- "../chrome/common/chrome_result_codes.h", + "crash/crash_helper.cc", + "crash/crash_helper.h", + ] ++ ++ if (enable_cef) { ++ sources += [ ++ "//cef/libcef/common/crash_reporter_client.cc", ++ "//cef/libcef/common/crash_reporter_client.h", ++ ++ # Required for crash_keys::GetChromeCrashKeys. ++ # Otherwise we need to copy this array into CEF, which would be difficult ++ # to maintain. ++ "//chrome/common/crash_keys.cc", ++ "//chrome/common/chrome_switches.cc", ++ "//components/flags_ui/flags_ui_switches.cc", ++ "//content/public/common/content_switches.cc", ++ ++ ] ++ include_dirs = [ ++ "//cef", ++ ] ++ } else { ++ sources += [ ++ "//chrome/app/chrome_crash_reporter_client_win.cc", ++ "//chrome/app/chrome_crash_reporter_client_win.h", ++ "//chrome/common/chrome_result_codes.h", ++ ] ++ } ++ + deps = [ + ":hook_util", + "//base:base", # This needs to go. DEP of app, crash_keys, client. + "//base:base_static", # pe_image ++ "//cef/libcef/features", + "//chrome/install_static:install_static_util", + "//components/crash/content/app:app", + "//components/crash/core/common", # crash_keys +diff --git chrome_elf/crash/crash_helper.cc chrome_elf/crash/crash_helper.cc +index c658fa9..8c4a145 100644 +--- chrome_elf/crash/crash_helper.cc ++++ chrome_elf/crash/crash_helper.cc +@@ -11,12 +11,17 @@ + #include + #include + ++#include "cef/libcef/features/features.h" + #include "chrome/app/chrome_crash_reporter_client_win.h" + #include "chrome_elf/hook_util/hook_util.h" + #include "components/crash/content/app/crashpad.h" + #include "components/crash/core/common/crash_keys.h" + #include "third_party/crashpad/crashpad/client/crashpad_client.h" + ++#if BUILDFLAG(ENABLE_CEF) ++#include "cef/libcef/common/crash_reporter_client.h" ++#endif ++ + namespace { + + // Crash handling from elf is only enabled for the chrome.exe process. +@@ -74,7 +79,11 @@ bool InitializeCrashReporting() { + g_crash_reports = new std::vector; + g_set_unhandled_exception_filter = new elf_hook::IATHook(); + ++#if BUILDFLAG(ENABLE_CEF) ++ CefCrashReporterClient::InitializeCrashReportingForProcess(); ++#else + ChromeCrashReporterClient::InitializeCrashReportingForProcess(); ++#endif + + g_crash_helper_enabled = true; + return true; +diff --git components/crash/content/app/breakpad_linux.cc components/crash/content/app/breakpad_linux.cc +index 9ebc33f..c013b36 100644 +--- components/crash/content/app/breakpad_linux.cc ++++ components/crash/content/app/breakpad_linux.cc +@@ -29,6 +29,7 @@ + #include "base/command_line.h" + #include "base/debug/crash_logging.h" + #include "base/debug/dump_without_crashing.h" ++#include "base/debug/leak_annotations.h" + #include "base/files/file_path.h" + #include "base/lazy_instance.h" + #include "base/linux_util.h" +@@ -89,6 +90,7 @@ namespace { + + #if !defined(OS_CHROMEOS) + const char kUploadURL[] = "https://clients2.google.com/cr/report"; ++const char* g_crash_server_url = kUploadURL; + #endif + + bool g_is_crash_reporter_enabled = false; +@@ -654,7 +656,7 @@ bool CrashDone(const MinidumpDescriptor& minidump, + info.process_type_length = 7; + info.distro = base::g_linux_distro; + info.distro_length = my_strlen(base::g_linux_distro); +- info.upload = upload; ++ info.upload = upload && g_crash_server_url; + info.process_start_time = g_process_start_time; + info.oom_size = base::g_oom_size; + info.pid = g_pid; +@@ -1275,7 +1277,7 @@ void ExecUploadProcessOrTerminate(const BreakpadInfo& info, + header_content_encoding, + header_content_type, + post_file, +- kUploadURL, ++ g_crash_server_url, + "--timeout=10", // Set a timeout so we don't hang forever. + "--tries=1", // Don't retry if the upload fails. + "-O", // output reply to fd 3 +@@ -1880,6 +1882,17 @@ void InitCrashReporter(const std::string& process_type) { + PostEnableBreakpadInitialization(); + } + ++void SetCrashServerURL(const std::string& url) { ++ if (url.empty()) { ++ g_crash_server_url = nullptr; ++ } else { ++ char* new_url = new char[url.size() + 1]; ++ ANNOTATE_LEAKING_OBJECT_PTR(new_url); ++ strcpy(new_url, url.c_str()); ++ g_crash_server_url = new_url; ++ } ++} ++ + #if defined(OS_ANDROID) + void InitNonBrowserCrashReporterForAndroid(const std::string& process_type) { + const base::CommandLine* command_line = +diff --git components/crash/content/app/breakpad_linux.h components/crash/content/app/breakpad_linux.h +index 3316fa0..df90dbd 100644 +--- components/crash/content/app/breakpad_linux.h ++++ components/crash/content/app/breakpad_linux.h +@@ -16,6 +16,9 @@ namespace breakpad { + // Turns on the crash reporter in any process. + extern void InitCrashReporter(const std::string& process_type); + ++// Set the crash server URL. ++void SetCrashServerURL(const std::string& url); ++ + #if defined(OS_ANDROID) + + const char kWebViewSingleProcessType[] = "webview"; +diff --git components/crash/content/app/crash_reporter_client.cc components/crash/content/app/crash_reporter_client.cc +index 3dfbd99..cb99c1e 100644 +--- components/crash/content/app/crash_reporter_client.cc ++++ components/crash/content/app/crash_reporter_client.cc +@@ -141,6 +141,26 @@ bool CrashReporterClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { + } + #endif + ++bool CrashReporterClient::EnableBreakpadForProcess( ++ const std::string& process_type) { ++ return false; ++} ++ ++std::string CrashReporterClient::GetCrashServerURL() { ++ return std::string(); ++} ++ ++void CrashReporterClient::GetCrashOptionalArguments( ++ std::vector* arguments) { ++} ++ ++#if defined(OS_WIN) ++base::string16 CrashReporterClient::GetCrashExternalHandler( ++ const base::string16& exe_dir) { ++ return exe_dir + L"\\crashpad_handler.exe"; ++} ++#endif ++ + #if defined(OS_ANDROID) + int CrashReporterClient::GetAndroidMinidumpDescriptor() { + return 0; +@@ -165,9 +185,4 @@ bool CrashReporterClient::ShouldEnableBreakpadMicrodumps() { + } + #endif + +-bool CrashReporterClient::EnableBreakpadForProcess( +- const std::string& process_type) { +- return false; +-} +- + } // namespace crash_reporter +diff --git components/crash/content/app/crash_reporter_client.h components/crash/content/app/crash_reporter_client.h +index 25ae505..349ee49 100644 +--- components/crash/content/app/crash_reporter_client.h ++++ components/crash/content/app/crash_reporter_client.h +@@ -8,6 +8,7 @@ + #include + + #include ++#include + + #include "base/strings/string16.h" + #include "build/build_config.h" +@@ -176,6 +177,17 @@ class CrashReporterClient { + + // Returns true if breakpad should run in the given process type. + virtual bool EnableBreakpadForProcess(const std::string& process_type); ++ ++ // Returns the URL for submitting crash reports. ++ virtual std::string GetCrashServerURL(); ++ ++ // Populate |arguments| with additional optional arguments. ++ virtual void GetCrashOptionalArguments(std::vector* arguments); ++ ++#if defined(OS_WIN) ++ // Returns the absolute path to the external crash handler exe. ++ virtual base::string16 GetCrashExternalHandler(const base::string16& exe_dir); ++#endif + }; + + } // namespace crash_reporter +diff --git components/crash/content/app/crashpad_mac.mm components/crash/content/app/crashpad_mac.mm +index 7df66ea..f841aea 100644 +--- components/crash/content/app/crashpad_mac.mm ++++ components/crash/content/app/crashpad_mac.mm +@@ -16,11 +16,14 @@ + #include "base/logging.h" + #include "base/mac/bundle_locations.h" + #include "base/mac/foundation_util.h" ++#include "base/path_service.h" + #include "base/strings/string_number_conversions.h" + #include "base/strings/string_piece.h" + #include "base/strings/stringprintf.h" + #include "base/strings/sys_string_conversions.h" + #include "components/crash/content/app/crash_reporter_client.h" ++#include "components/crash/content/app/crash_switches.h" ++#include "content/public/common/content_paths.h" + #include "third_party/crashpad/crashpad/client/crash_report_database.h" + #include "third_party/crashpad/crashpad/client/crashpad_client.h" + #include "third_party/crashpad/crashpad/client/crashpad_info.h" +@@ -40,9 +43,10 @@ + + if (initial_client) { + @autoreleasepool { +- base::FilePath framework_bundle_path = base::mac::FrameworkBundlePath(); +- base::FilePath handler_path = +- framework_bundle_path.Append("Helpers").Append("crashpad_handler"); ++ // Use the same subprocess helper exe. ++ base::FilePath handler_path; ++ PathService::Get(content::CHILD_PROCESS_EXE, &handler_path); ++ DCHECK(!handler_path.empty()); + + // Is there a way to recover if this fails? + CrashReporterClient* crash_reporter_client = GetCrashReporterClient(); +@@ -54,7 +58,7 @@ + // crash server won't have symbols for any other build types. + std::string url = "https://clients2.google.com/cr/report"; + #else +- std::string url; ++ std::string url = crash_reporter_client->GetCrashServerURL(); + #endif + + std::map process_annotations; +@@ -90,6 +94,12 @@ + "--reset-own-crash-exception-port-to-system-default"); + } + ++ // Since we're using the same subprocess helper exe we must specify the ++ // process type. ++ arguments.push_back(std::string("--type=") + switches::kCrashpadHandler); ++ ++ crash_reporter_client->GetCrashOptionalArguments(&arguments); ++ + crashpad::CrashpadClient crashpad_client; + bool result = crashpad_client.StartHandler(handler_path, + database_path, +diff --git components/crash/content/app/crashpad_win.cc components/crash/content/app/crashpad_win.cc +index a22af31..bc5086e 100644 +--- components/crash/content/app/crashpad_win.cc ++++ components/crash/content/app/crashpad_win.cc +@@ -81,7 +81,7 @@ base::FilePath PlatformCrashpadInitialization(bool initial_client, + #if defined(GOOGLE_CHROME_BUILD) + std::string url = "https://clients2.google.com/cr/report"; + #else +- std::string url; ++ std::string url = crash_reporter_client->GetCrashServerURL(); + #endif + + // Allow the crash server to be overridden for testing. If the variable +@@ -115,9 +115,12 @@ base::FilePath PlatformCrashpadInitialization(bool initial_client, + arguments.push_back("/prefetch:7"); + } else { + base::FilePath exe_dir = exe_file.DirName(); +- exe_file = exe_dir.Append(FILE_PATH_LITERAL("crashpad_handler.exe")); ++ exe_file = base::FilePath( ++ crash_reporter_client->GetCrashExternalHandler(exe_dir.value())); + } + ++ crash_reporter_client->GetCrashOptionalArguments(&arguments); ++ + g_crashpad_client.Get().StartHandler( + exe_file, database_path, metrics_path, url, process_annotations, + arguments, false, false); +diff --git content/browser/frame_host/debug_urls.cc content/browser/frame_host/debug_urls.cc +index 2e61cc1..6b8b943 100644 +--- content/browser/frame_host/debug_urls.cc ++++ content/browser/frame_host/debug_urls.cc +@@ -189,7 +189,9 @@ bool HandleDebugURL(const GURL& url, ui::PageTransition transition) { + cc::switches::kEnableGpuBenchmarking) && + (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)); + +- if (!(transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && ++ // CEF does not use PAGE_TRANSITION_FROM_ADDRESS_BAR. ++ if (!(transition & (ui::PAGE_TRANSITION_TYPED || ++ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)) && + !is_telemetry_navigation) + return false; + diff --git a/patch/patches/crashpad_tp_1995.patch b/patch/patches/crashpad_tp_1995.patch new file mode 100644 index 000000000..0abfaf933 --- /dev/null +++ b/patch/patches/crashpad_tp_1995.patch @@ -0,0 +1,290 @@ +diff --git crashpad/client/prune_crash_reports.cc crashpad/client/prune_crash_reports.cc +index 3aaaeee..d99fcb4 100644 +--- crashpad/client/prune_crash_reports.cc ++++ crashpad/client/prune_crash_reports.cc +@@ -67,13 +67,19 @@ void PruneCrashReportDatabase(CrashReportDatabase* database, + } + + // static +-std::unique_ptr PruneCondition::GetDefault() { ++std::unique_ptr PruneCondition::GetDefault( ++ int max_size_in_mb, ++ int max_age_in_days) { + // DatabaseSizePruneCondition must be the LHS so that it is always evaluated, + // due to the short-circuting behavior of BinaryPruneCondition. ++ if (max_size_in_mb <= 0) ++ max_size_in_mb = 128; ++ if (max_age_in_days <= 0) ++ max_age_in_days = 365; + return base::WrapUnique( + new BinaryPruneCondition(BinaryPruneCondition::OR, +- new DatabaseSizePruneCondition(1024 * 128), +- new AgePruneCondition(365))); ++ new DatabaseSizePruneCondition(max_size_in_mb), ++ new AgePruneCondition(max_age_in_days))); + } + + static const time_t kSecondsInDay = 60 * 60 * 24; +diff --git crashpad/client/prune_crash_reports.h crashpad/client/prune_crash_reports.h +index b66e9349..86d1f15 100644 +--- crashpad/client/prune_crash_reports.h ++++ crashpad/client/prune_crash_reports.h +@@ -57,7 +57,8 @@ class PruneCondition { + //! of 128 MB. + //! + //! \return A PruneCondition for use with PruneCrashReportDatabase(). +- static std::unique_ptr GetDefault(); ++ static std::unique_ptr GetDefault(int max_size_in_mb, ++ int max_age_in_days); + + virtual ~PruneCondition() {} + +diff --git crashpad/client/settings.cc crashpad/client/settings.cc +index d018e37f..47d8110 100644 +--- crashpad/client/settings.cc ++++ crashpad/client/settings.cc +@@ -38,7 +38,7 @@ void ScopedLockedFileHandleTraits::Free(FileHandle handle) { + + struct Settings::Data { + static const uint32_t kSettingsMagic = 'CPds'; +- static const uint32_t kSettingsVersion = 1; ++ static const uint32_t kSettingsVersion = 2; + + enum Options : uint32_t { + kUploadsEnabled = 1 << 0, +@@ -49,6 +49,9 @@ struct Settings::Data { + options(0), + padding_0(0), + last_upload_attempt_time(0), ++ next_upload_attempt_time(0), ++ backoff_step(0), ++ padding_1(0), + client_id() {} + + uint32_t magic; +@@ -56,6 +59,9 @@ struct Settings::Data { + uint32_t options; + uint32_t padding_0; + uint64_t last_upload_attempt_time; // time_t ++ uint64_t next_upload_attempt_time; // time_t ++ uint32_t backoff_step; ++ uint32_t padding_1; + UUID client_id; + }; + +@@ -141,6 +147,56 @@ bool Settings::SetLastUploadAttemptTime(time_t time) { + return WriteSettings(handle.get(), settings); + } + ++bool Settings::GetNextUploadAttemptTime(time_t* time) { ++ DCHECK(initialized_.is_valid()); ++ ++ Data settings; ++ if (!OpenAndReadSettings(&settings)) ++ return false; ++ ++ *time = InRangeCast(settings.next_upload_attempt_time, ++ std::numeric_limits::max()); ++ return true; ++} ++ ++bool Settings::SetNextUploadAttemptTime(time_t time) { ++ DCHECK(initialized_.is_valid()); ++ ++ Data settings; ++ ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); ++ if (!handle.is_valid()) ++ return false; ++ ++ settings.next_upload_attempt_time = InRangeCast(time, 0); ++ ++ return WriteSettings(handle.get(), settings); ++} ++ ++bool Settings::GetBackoffStep(int* step) { ++ DCHECK(initialized_.is_valid()); ++ ++ Data settings; ++ if (!OpenAndReadSettings(&settings)) ++ return false; ++ ++ *step = InRangeCast(settings.backoff_step, ++ std::numeric_limits::max()); ++ return true; ++} ++ ++bool Settings::SetBackoffStep(int step) { ++ DCHECK(initialized_.is_valid()); ++ ++ Data settings; ++ ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); ++ if (!handle.is_valid()) ++ return false; ++ ++ settings.backoff_step = InRangeCast(step, 0); ++ ++ return WriteSettings(handle.get(), settings); ++} ++ + // static + Settings::ScopedLockedFileHandle Settings::MakeScopedLockedFileHandle( + FileHandle file, +diff --git crashpad/client/settings.h crashpad/client/settings.h +index b64f74f..0c3c22e 100644 +--- crashpad/client/settings.h ++++ crashpad/client/settings.h +@@ -102,6 +102,11 @@ class Settings { + //! error logged. + bool SetLastUploadAttemptTime(time_t time); + ++ bool GetNextUploadAttemptTime(time_t* time); ++ bool SetNextUploadAttemptTime(time_t time); ++ bool GetBackoffStep(int* step); ++ bool SetBackoffStep(int step); ++ + private: + struct Data; + +diff --git crashpad/handler/crash_report_upload_thread.h crashpad/handler/crash_report_upload_thread.h +index a9601d1..9517730 100644 +--- crashpad/handler/crash_report_upload_thread.h ++++ crashpad/handler/crash_report_upload_thread.h +@@ -76,7 +76,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate { + //! This method may be called from any thread. + void ReportPending(); + +- private: ++ protected: + //! \brief The result code from UploadReport(). + enum class UploadResult { + //! \brief The crash report was uploaded successfully. +@@ -99,7 +99,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate { + + //! \brief Obtains all pending reports from the database, and calls + //! ProcessPendingReport() to process each one. +- void ProcessPendingReports(); ++ virtual void ProcessPendingReports(); + + //! \brief Processes a single pending report from the database. + //! +@@ -113,7 +113,7 @@ class CrashReportUploadThread : public WorkerThread::Delegate { + //! remain in the “pending” state. If the upload fails and no more retries are + //! desired, or report upload is disabled, it will be marked as “completed” in + //! the database without ever having been uploaded. +- void ProcessPendingReport(const CrashReportDatabase::Report& report); ++ virtual void ProcessPendingReport(const CrashReportDatabase::Report& report); + + //! \brief Attempts to upload a crash report. + //! +diff --git crashpad/handler/handler_main.cc crashpad/handler/handler_main.cc +index 29c5ddc..7a6bad7 100644 +--- crashpad/handler/handler_main.cc ++++ crashpad/handler/handler_main.cc +@@ -29,8 +29,10 @@ + #include "base/logging.h" + #include "base/metrics/persistent_histogram_allocator.h" + #include "base/scoped_generic.h" ++#include "base/strings/string_number_conversions.h" + #include "base/strings/utf_string_conversions.h" + #include "build/build_config.h" ++#include "cef/libcef/features/features.h" + #include "client/crash_report_database.h" + #include "client/crashpad_client.h" + #include "client/prune_crash_reports.h" +@@ -62,6 +64,10 @@ + #include "util/win/initial_client_data.h" + #endif // OS_MACOSX + ++#if BUILDFLAG(ENABLE_CEF) ++#include "cef/libcef/common/cef_crash_report_upload_thread.h" ++#endif ++ + namespace crashpad { + + namespace { +@@ -168,6 +174,9 @@ int HandlerMain(int argc, char* argv[]) { + kOptionPipeName, + #endif // OS_MACOSX + kOptionURL, ++ kOptionMaxUploads, ++ kOptionMaxDatabaseSize, ++ kOptionMaxDatabaseAge, + + // Standard options. + kOptionHelp = -2, +@@ -188,11 +197,17 @@ int HandlerMain(int argc, char* argv[]) { + InitialClientData initial_client_data; + #endif // OS_MACOSX + bool rate_limit; ++ int max_uploads; ++ int max_database_size; ++ int max_database_age; + } options = {}; + #if defined(OS_MACOSX) + options.handshake_fd = -1; + #endif + options.rate_limit = true; ++ options.max_uploads = 0; ++ options.max_database_size = 0; ++ options.max_database_age = 0; + + const option long_options[] = { + {"annotation", required_argument, nullptr, kOptionAnnotation}, +@@ -222,6 +237,9 @@ int HandlerMain(int argc, char* argv[]) { + {"url", required_argument, nullptr, kOptionURL}, + {"help", no_argument, nullptr, kOptionHelp}, + {"version", no_argument, nullptr, kOptionVersion}, ++ {"max-uploads", required_argument, nullptr, kOptionMaxUploads}, ++ {"max-db-size", required_argument, nullptr, kOptionMaxDatabaseSize}, ++ {"max-db-age", required_argument, nullptr, kOptionMaxDatabaseAge}, + {nullptr, 0, nullptr, 0}, + }; + +@@ -293,6 +311,27 @@ int HandlerMain(int argc, char* argv[]) { + options.url = optarg; + break; + } ++ case kOptionMaxUploads: { ++ if (base::StringToInt(optarg, &options.max_uploads)) { ++ if (options.max_uploads < 0) ++ options.max_uploads = 0; ++ } ++ break; ++ } ++ case kOptionMaxDatabaseSize: { ++ if (base::StringToInt(optarg, &options.max_database_size)) { ++ if (options.max_database_size < 0) ++ options.max_database_size = 0; ++ } ++ break; ++ } ++ case kOptionMaxDatabaseAge: { ++ if (base::StringToInt(optarg, &options.max_database_age)) { ++ if (options.max_database_age < 0) ++ options.max_database_age = 0; ++ } ++ break; ++ } + case kOptionHelp: { + Usage(me); + return EXIT_SUCCESS; +@@ -425,12 +464,18 @@ int HandlerMain(int argc, char* argv[]) { + // TODO(scottmg): options.rate_limit should be removed when we have a + // configurable database setting to control upload limiting. + // See https://crashpad.chromium.org/bug/23. ++#if BUILDFLAG(ENABLE_CEF) ++ CefCrashReportUploadThread upload_thread( ++ database.get(), options.url, options.rate_limit, options.max_uploads); ++#else + CrashReportUploadThread upload_thread( + database.get(), options.url, options.rate_limit); ++#endif + upload_thread.Start(); + + PruneCrashReportThread prune_thread(database.get(), +- PruneCondition::GetDefault()); ++ PruneCondition::GetDefault(options.max_database_size, ++ options.max_database_age)); + prune_thread.Start(); + + CrashReportExceptionHandler exception_handler( diff --git a/tests/cefclient/browser/client_app_delegates_browser.cc b/tests/cefclient/browser/client_app_delegates_browser.cc index 6429c41b3..83128e8aa 100644 --- a/tests/cefclient/browser/client_app_delegates_browser.cc +++ b/tests/cefclient/browser/client_app_delegates_browser.cc @@ -4,6 +4,8 @@ #include "tests/shared/browser/client_app_browser.h" +#include "tests/cefclient/browser/client_browser.h" + #if defined(OS_LINUX) #include "tests/cefclient/browser/print_handler_gtk.h" #endif @@ -12,6 +14,7 @@ namespace client { // static void ClientAppBrowser::CreateDelegates(DelegateSet& delegates) { + browser::CreateDelegates(delegates); } // static diff --git a/tests/cefclient/browser/client_browser.cc b/tests/cefclient/browser/client_browser.cc new file mode 100644 index 000000000..f446dc83e --- /dev/null +++ b/tests/cefclient/browser/client_browser.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include "tests/cefclient/browser/client_browser.h" + +#include "include/cef_crash_util.h" + +namespace client { +namespace browser { + +namespace { + +class ClientBrowserDelegate : public ClientAppBrowser::Delegate { + public: + ClientBrowserDelegate() {} + + void OnContextInitialized(CefRefPtr app) OVERRIDE { + if (CefCrashReportingEnabled()) { + // Set some crash keys for testing purposes. Keys must be defined in the + // "crash_reporter.cfg" file. See cef_crash_util.h for details. + CefSetCrashKeyValue("testkey1", "value1_browser"); + CefSetCrashKeyValue("testkey2", "value2_browser"); + CefSetCrashKeyValue("testkey3", "value3_browser"); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(ClientBrowserDelegate); + IMPLEMENT_REFCOUNTING(ClientBrowserDelegate); +}; + +} // namespace + +void CreateDelegates(ClientAppBrowser::DelegateSet& delegates) { + delegates.insert(new ClientBrowserDelegate); +} + +} // namespace browser +} // namespace client diff --git a/tests/cefclient/browser/client_browser.h b/tests/cefclient/browser/client_browser.h new file mode 100644 index 000000000..ac09e422b --- /dev/null +++ b/tests/cefclient/browser/client_browser.h @@ -0,0 +1,21 @@ +// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_BROWSER_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_BROWSER_H_ +#pragma once + +#include "include/cef_base.h" +#include "tests/shared/browser/client_app_browser.h" + +namespace client { +namespace browser { + +// Create the browser delegate. Called from client_app_delegates_browser.cc. +void CreateDelegates(ClientAppBrowser::DelegateSet& delegates); + +} // namespace browser +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_BROWSER_H_ diff --git a/tests/cefclient/renderer/client_renderer.cc b/tests/cefclient/renderer/client_renderer.cc index 9e160c4af..b04554b13 100644 --- a/tests/cefclient/renderer/client_renderer.cc +++ b/tests/cefclient/renderer/client_renderer.cc @@ -7,6 +7,7 @@ #include #include +#include "include/cef_crash_util.h" #include "include/cef_dom.h" #include "include/wrapper/cef_helpers.h" #include "include/wrapper/cef_message_router.h" @@ -25,30 +26,42 @@ class ClientRenderDelegate : public ClientAppRenderer::Delegate { : last_node_is_editable_(false) { } - virtual void OnWebKitInitialized(CefRefPtr app) OVERRIDE { + void OnRenderThreadCreated( + CefRefPtr app, + CefRefPtr extra_info) OVERRIDE { + if (CefCrashReportingEnabled()) { + // Set some crash keys for testing purposes. Keys must be defined in the + // "crash_reporter.cfg" file. See cef_crash_util.h for details. + CefSetCrashKeyValue("testkey1", "value1_renderer"); + CefSetCrashKeyValue("testkey2", "value2_renderer"); + CefSetCrashKeyValue("testkey3", "value3_renderer"); + } + } + + void OnWebKitInitialized(CefRefPtr app) OVERRIDE { // Create the renderer-side router for query handling. CefMessageRouterConfig config; message_router_ = CefMessageRouterRendererSide::Create(config); } - virtual void OnContextCreated(CefRefPtr app, - CefRefPtr browser, - CefRefPtr frame, - CefRefPtr context) OVERRIDE { + void OnContextCreated(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) OVERRIDE { message_router_->OnContextCreated(browser, frame, context); } - virtual void OnContextReleased(CefRefPtr app, - CefRefPtr browser, - CefRefPtr frame, - CefRefPtr context) OVERRIDE { + void OnContextReleased(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) OVERRIDE { message_router_->OnContextReleased(browser, frame, context); } - virtual void OnFocusedNodeChanged(CefRefPtr app, - CefRefPtr browser, - CefRefPtr frame, - CefRefPtr node) OVERRIDE { + void OnFocusedNodeChanged(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr node) OVERRIDE { bool is_editable = (node.get() && node->IsEditable()); if (is_editable != last_node_is_editable_) { // Notify the browser of the change in focused element type. @@ -60,7 +73,7 @@ class ClientRenderDelegate : public ClientAppRenderer::Delegate { } } - virtual bool OnProcessMessageReceived( + bool OnProcessMessageReceived( CefRefPtr app, CefRefPtr browser, CefProcessId source_process, @@ -75,6 +88,7 @@ class ClientRenderDelegate : public ClientAppRenderer::Delegate { // Handles the renderer side of query routing. CefRefPtr message_router_; + DISALLOW_COPY_AND_ASSIGN(ClientRenderDelegate); IMPLEMENT_REFCOUNTING(ClientRenderDelegate); }; diff --git a/tools/crash_server.py b/tools/crash_server.py new file mode 100644 index 000000000..8d39a1786 --- /dev/null +++ b/tools/crash_server.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Embedded Framework Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be found +# in the LICENSE file. + +""" +This script implements a simple HTTP server for receiving crash report uploads +from a Breakpad/Crashpad client (any CEF-based application). This script is +intended for testing purposes only. An HTTPS server and a system such as Socorro +(https://wiki.mozilla.org/Socorro) should be used when uploading crash reports +from production applications. + +Usage of this script is as follows: + +1. Run this script from the command-line. The first argument is the server port + number and the second argument is the directory where uploaded report + information will be saved: + + > python crash_server.py 8080 /path/to/dumps + +2. Create a "crash_reporter.cfg" file at the required platform-specific + location. On Windows and Linux this file must be placed next to the main + application executable. On macOS this file must be placed in the top-level + app bundle Resources directory (e.g. ".app/Contents/Resources"). At + a minimum it must contain a "ServerURL=http://localhost:8080" line under the + "[Config]" section (make sure the port number matches the value specified in + step 1). See comments in include/cef_crash_util.h for a complete + specification of this file. + + Example file contents: + + [Config] + ServerURL=http://localhost:8080 + # Disable rate limiting so that all crashes are uploaded. + RateLimitEnabled=false + MaxUploadsPerDay=0 + + [CrashKeys] + # The cefclient sample application sets these values (see step 5 below). + testkey1=small + testkey2=medium + testkey3=large + +3. Load one of the following URLs in the CEF-based application to cause a crash: + + Main (browser) process crash: chrome://inducebrowsercrashforrealz + Renderer process crash: chrome://crash + GPU process crash: chrome://gpucrash + +4. When this script successfully receives a crash report upload you will see + console output like the following: + + 01/10/2017 12:31:23: Dump + + The "" value is a 16 digit hexadecimal string that uniquely identifies + the dump. Crash dumps and metadata (product state, command-line flags, crash + keys, etc.) will be written to the ".dmp" and ".json" files + underneath the directory specified in step 1. + + On Linux Breakpad uses the wget utility to upload crash dumps, so make sure + that utility is installed. If the crash is handled correctly then you should + see console output like the following when the client uploads a crash dump: + + --2017-01-10 12:31:22-- http://localhost:8080/ + Resolving localhost (localhost)... 127.0.0.1 + Connecting to localhost (localhost)|127.0.0.1|:8080... connected. + HTTP request sent, awaiting response... 200 OK + Length: unspecified [text/html] + Saving to: '/dev/fd/3' + Crash dump id: + + On macOS when uploading a crash report to this script over HTTP you may + receive an error like the following: + + "Transport security has blocked a cleartext HTTP (http://) resource load + since it is insecure. Temporary exceptions can be configured via your app's + Info.plist file." + + You can work around this error by adding the following key to the Helper app + Info.plist file (e.g. ".app/Contents/Frameworks/ + Helper.app/Contents/Info.plist"): + + NSAppTransportSecurity + + + NSAllowsArbitraryLoads + + + +5. The cefclient sample application sets test crash key values in the browser + and renderer processes. To work properly these values must also be defined + in the "[CrashKeys]" section of "crash_reporter.cfg" as shown above. + + In tests/cefclient/browser/client_browser.cc (browser process): + + CefSetCrashKeyValue("testkey1", "value1_browser"); + CefSetCrashKeyValue("testkey2", "value2_browser"); + CefSetCrashKeyValue("testkey3", "value3_browser"); + + In tests/cefclient/renderer/client_renderer.cc (renderer process): + + CefSetCrashKeyValue("testkey1", "value1_renderer"); + CefSetCrashKeyValue("testkey2", "value2_renderer"); + CefSetCrashKeyValue("testkey3", "value3_renderer"); + + When crashing the browser or renderer processes with cefclient you should + verify that the test crash key values are included in the metadata + (".json") file. Some values may be chunked as described in + include/cef_crash_util.h. +""" + +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import cgi +import cStringIO +import datetime +import json +import os +import shutil +import sys +import uuid +import zlib + +def print_msg(msg): + """ Write |msg| to stdout and flush. """ + timestr = datetime.datetime.now().strftime("%m/%d/%Y %H:%M:%S") + sys.stdout.write("%s: %s\n" % (timestr, msg)) + sys.stdout.flush() + +# Key identifying the minidump file. +minidump_key = 'upload_file_minidump' + +class CrashHTTPRequestHandler(BaseHTTPRequestHandler): + def __init__(self, dump_directory, *args): + self._dump_directory = dump_directory + BaseHTTPRequestHandler.__init__(self, *args) + + def _send_default_response_headers(self): + """ Send default response headers. """ + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def _parse_post_data(self): + """ Returns a cgi.FieldStorage object for this request or None if this is + not a POST request. """ + if self.command != 'POST': + return None + return cgi.FieldStorage( + fp = self.rfile, + headers = self.headers, + environ = { + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': self.headers['Content-Type'], + }) + + def _create_new_dump_id(self): + """ Breakpad requires a 16 digit hexadecimal dump ID. """ + return str(uuid.uuid4().get_hex().upper()[0:16]) + + def do_GET(self): + """ Default empty implementation for handling GET requests. """ + self._send_default_response_headers() + self.wfile.write("

GET!

") + + def do_HEAD(self): + """ Default empty implementation for handling HEAD requests. """ + self._send_default_response_headers() + + def do_POST(self): + """ Handle a multi-part POST request submitted by Breakpad/Crashpad. """ + self._send_default_response_headers() + + # Create a unique ID for the dump. + dump_id = self._create_new_dump_id() + + # Return the unique ID to the caller. + self.wfile.write(dump_id) + + dmp_stream = None + metadata = {} + + # Breakpad on Linux sends gzipped request contents. + if 'Content-Encoding' in self.headers and self.headers['Content-Encoding'] == 'gzip': + print_msg('Decompressing gzipped request') + self.rfile = cStringIO.StringIO(zlib.decompress(self.rfile.read(), 16+zlib.MAX_WBITS)) + + # Parse the multi-part request. + form_data = self._parse_post_data() + for key in form_data.keys(): + if key == minidump_key and form_data[minidump_key].file: + dmp_stream = form_data[minidump_key].file + else: + metadata[key] = form_data[key].value + + if dmp_stream is None: + # Exit early if the request is invalid. + print_msg('Invalid dump %s' % dump_id) + return + + print_msg('Dump %s' % dump_id) + + # Write the minidump to file. + dump_file = os.path.join(self._dump_directory, dump_id + '.dmp') + with open(dump_file, 'wb') as fp: + shutil.copyfileobj(dmp_stream, fp) + + # Write the metadata to file. + meta_file = os.path.join(self._dump_directory, dump_id + '.json') + with open(meta_file, 'w') as fp: + json.dump(metadata, fp) + +def HandleRequestsUsing(dump_store): + return lambda *args: CrashHTTPRequestHandler(dump_directory, *args) + +def RunCrashServer(port, dump_directory): + """ Run the crash handler HTTP server. """ + httpd = HTTPServer(('', port), HandleRequestsUsing(dump_directory)) + print_msg('Starting httpd on port %d' % port) + httpd.serve_forever() + +# Program entry point. +if __name__ == "__main__": + if len(sys.argv) != 3: + print 'Usage: %s ' % os.path.basename(sys.argv[0]) + sys.exit(1) + + # Create the dump directory if necessary. + dump_directory = sys.argv[2] + if not os.path.exists(dump_directory): + os.makedirs(dump_directory) + if not os.path.isdir(dump_directory): + raise Exception('Directory does not exist: %s' % dump_directory) + + RunCrashServer(int(sys.argv[1]), dump_directory) +