crash: Add private API for reporting JavaScript errors.
We add a new private API for Chrome OS component extensions to report
JavaScript errors to Crash. The API mirrors the window.onerror API.
Design doc:
https://docs.google.com/document/d/1XqN_wO1_UfVRTfhDf6yzHCyBwq_TcSw8ILDOOY_h3w4/edit?usp=sharing
Bug: 986178
Change-Id: I8e11553fd7d1c07c712c7a87a68f1c0ccace54f1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1732329
Commit-Queue: Darren Shen <[email protected]>
Reviewed-by: Giovanni Ortuño Urquidi <[email protected]>
Reviewed-by: Devlin <[email protected]>
Reviewed-by: Robert Sesek <[email protected]>
Reviewed-by: Bruce Dawson <[email protected]>
Reviewed-by: Chris Palmer <[email protected]>
Cr-Commit-Position: refs/heads/master@{#719493}
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 263437d..ed37429 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -504,6 +504,7 @@
sources += [
"api/audio/audio_apitest_chromeos.cc",
"api/cec_private/cec_private_apitest.cc",
+ "api/crash_report_private/crash_report_private_apitest.cc",
"api/media_perception_private/media_perception_private_apitest.cc",
"api/system_power_source/system_power_source_apitest.cc",
"api/virtual_keyboard/virtual_keyboard_apitest.cc",
@@ -521,6 +522,7 @@
"//chromeos/dbus/upstart",
"//chromeos/login/login_state",
"//chromeos/network",
+ "//components/crash/content/app:app",
]
}
}
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn
index d394098..3329155 100644
--- a/extensions/browser/api/BUILD.gn
+++ b/extensions/browser/api/BUILD.gn
@@ -131,6 +131,7 @@
public_deps += [
"//extensions/browser/api/cec_private",
"//extensions/browser/api/clipboard",
+ "//extensions/browser/api/crash_report_private",
"//extensions/browser/api/diagnostics",
"//extensions/browser/api/networking_config",
"//extensions/browser/api/system_power_source",
diff --git a/extensions/browser/api/crash_report_private/BUILD.gn b/extensions/browser/api/crash_report_private/BUILD.gn
new file mode 100644
index 0000000..da1fc86
--- /dev/null
+++ b/extensions/browser/api/crash_report_private/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 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.
+
+import("//extensions/buildflags/buildflags.gni")
+
+assert(enable_extensions,
+ "Cannot depend on extensions because enable_extensions=false.")
+
+source_set("crash_report_private") {
+ sources = [
+ "crash_report_private_api.cc",
+ "crash_report_private_api.h",
+ ]
+
+ deps = [
+ "//components/crash/content/app",
+ "//content/public/browser",
+ "//extensions/common/api",
+ "//net",
+ "//services/network:network_service",
+ "//services/network/public/cpp",
+ ]
+}
diff --git a/extensions/browser/api/crash_report_private/DEPS b/extensions/browser/api/crash_report_private/DEPS
new file mode 100644
index 0000000..392684af8
--- /dev/null
+++ b/extensions/browser/api/crash_report_private/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+components/crash/content/app/client_upload_info.h",
+]
+
+specific_include_rules = {
+ "crash_report_private_apitest.cc": [
+ "+components/crash/content/app/crash_reporter_client.h",
+ ],
+}
diff --git a/extensions/browser/api/crash_report_private/OWNERS b/extensions/browser/api/crash_report_private/OWNERS
new file mode 100644
index 0000000..c7441c16
--- /dev/null
+++ b/extensions/browser/api/crash_report_private/OWNERS
@@ -0,0 +1,4 @@
[email protected]
[email protected]
+
+# COMPONENT: Platform>Apps>SystemWebApps
diff --git a/extensions/browser/api/crash_report_private/crash_report_private_api.cc b/extensions/browser/api/crash_report_private/crash_report_private_api.cc
new file mode 100644
index 0000000..9d58eb6
--- /dev/null
+++ b/extensions/browser/api/crash_report_private/crash_report_private_api.cc
@@ -0,0 +1,238 @@
+// Copyright 2019 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 "extensions/browser/api/crash_report_private/crash_report_private_api.h"
+
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "base/task/post_task.h"
+#include "base/time/default_clock.h"
+#include "components/crash/content/app/client_upload_info.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "extensions/common/api/crash_report_private.h"
+#include "net/base/escape.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+namespace extensions {
+namespace api {
+
+namespace {
+
+// Used for throttling the API calls.
+base::Time g_last_called_time;
+
+base::Clock* g_clock = base::DefaultClock::GetInstance();
+
+#if defined(GOOGLE_CHROME_BUILD)
+constexpr char kCrashEndpointUrl[] = "https://clients2.google.com/cr/report";
+#else
+constexpr char kCrashEndpointUrl[] = "";
+#endif
+
+std::string& GetCrashEndpoint() {
+ static base::NoDestructor<std::string> crash_endpoint(kCrashEndpointUrl);
+ return *crash_endpoint;
+}
+
+constexpr int kCrashEndpointResponseMaxSizeInBytes = 1024;
+
+void OnRequestComplete(std::unique_ptr<network::SimpleURLLoader> url_loader,
+ base::OnceCallback<void()> callback,
+ std::unique_ptr<std::string> response_body) {
+ if (response_body) {
+ DVLOG(1) << "Uploaded crash report. ID: " << *response_body;
+ } else {
+ LOG(ERROR) << "Failed to upload crash report";
+ }
+ std::move(callback).Run();
+}
+
+// Sometimes, the stack trace will contain an error message as the first line,
+// which confuses the Crash server. This function deletes it if it is present.
+std::string RemoveErrorMessageFromStackTrace(const std::string& stack_trace,
+ const std::string& error_message) {
+ // Return the original stack trace if the error message is not present.
+ const auto error_message_index = stack_trace.find(error_message);
+ if (error_message_index == std::string::npos)
+ return stack_trace;
+
+ // If the stack trace only contains one line, then delete the whole trace.
+ const auto first_line_end_index = stack_trace.find('\n');
+ if (first_line_end_index == std::string::npos)
+ return std::string();
+
+ // Otherwise, delete the first line.
+ return stack_trace.substr(first_line_end_index + 1);
+}
+
+using ParameterMap = std::map<std::string, std::string>;
+
+std::string BuildPostRequestQueryString(const ParameterMap& params) {
+ std::vector<std::string> query_parts;
+ for (const auto& kv : params) {
+ query_parts.push_back(base::StrCat(
+ {kv.first, "=",
+ net::EscapeQueryParamValue(kv.second, /* use_plus */ false)}));
+ }
+ return base::JoinString(query_parts, "&");
+}
+
+struct PlatformInfo {
+ std::string product_name;
+ std::string version;
+ std::string channel;
+ std::string platform;
+ std::string os_version;
+};
+
+PlatformInfo GetPlatformInfo() {
+ PlatformInfo info;
+ crash_reporter::GetClientProductNameAndVersion(&info.product_name,
+ &info.version, &info.channel);
+
+ int32_t os_major_version = 0;
+ int32_t os_minor_version = 0;
+ int32_t os_bugfix_version = 0;
+ base::SysInfo::OperatingSystemVersionNumbers(
+ &os_major_version, &os_minor_version, &os_bugfix_version);
+
+ info.os_version = base::StringPrintf("%d.%d.%d", os_major_version,
+ os_minor_version, os_bugfix_version);
+ return info;
+}
+
+void SendReport(network::mojom::URLLoaderFactory* loader_factory,
+ const GURL& url,
+ const std::string& body,
+ base::OnceCallback<void()> callback) {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->method = "POST";
+ resource_request->url = url;
+
+ const auto traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("javascript_report_error", R"(
+ semantics {
+ sender: "JavaScript error reporter"
+ description:
+ "Chrome can send JavaScript errors that occur within built-in "
+ "component extensions. If enabled, the error message, along "
+ "with information about Chrome and the operating system."
+ trigger:
+ "A JavaScript error occurs in a Chrome component extension"
+ data:
+ "The JavaScript error message, the version and channel of Chrome, "
+ "the URL of the extension, the line and column number where the "
+ "error occurred, and a stack trace of the error."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ )");
+
+ DVLOG(1) << "Sending crash report: " << resource_request->url;
+
+ auto url_loader = network::SimpleURLLoader::Create(
+ std::move(resource_request), traffic_annotation);
+
+ if (!body.empty()) {
+ url_loader->AttachStringForUpload(body, "text/plain");
+ }
+
+ network::SimpleURLLoader* loader = url_loader.get();
+ loader->DownloadToString(
+ loader_factory,
+ base::BindOnce(&OnRequestComplete, std::move(url_loader),
+ std::move(callback)),
+ kCrashEndpointResponseMaxSizeInBytes);
+}
+
+void ReportJavaScriptError(network::mojom::URLLoaderFactory* loader_factory,
+ const crash_report_private::ErrorInfo& error,
+ base::OnceCallback<void()> callback) {
+ const auto platform = GetPlatformInfo();
+
+ const GURL source(error.url);
+ const auto product = error.product ? *error.product : platform.product_name;
+ const auto version = error.version ? *error.version : platform.version;
+
+ ParameterMap params;
+ params["prod"] = net::EscapeQueryParamValue(product, /* use_plus */ false);
+ params["ver"] = net::EscapeQueryParamValue(version, /* use_plus */ false);
+ params["type"] = "JavascriptError";
+ // TODO(https://crbug.com/986178): Include |error.message| once we scrub PII.
+ params["browser"] = "Chrome";
+ params["browser_version"] = platform.version;
+ params["channel"] = platform.channel;
+ params["os"] = "ChromeOS";
+ params["os_version"] = platform.os_version;
+ params["full_url"] = source.spec();
+ params["url"] = source.path();
+ params["src"] = source.spec();
+ if (error.line_number)
+ params["line"] = *error.line_number;
+ if (error.column_number)
+ params["column"] = *error.column_number;
+
+ // The network request must be made on the UI thread.
+ const GURL url(base::StrCat(
+ {GetCrashEndpoint(), "?", BuildPostRequestQueryString(params)}));
+ const std::string body =
+ error.stack_trace
+ ? RemoveErrorMessageFromStackTrace(*error.stack_trace, error.message)
+ : "";
+
+ SendReport(loader_factory, url, body, std::move(callback));
+}
+
+} // namespace
+
+CrashReportPrivateReportErrorFunction::CrashReportPrivateReportErrorFunction() =
+ default;
+
+CrashReportPrivateReportErrorFunction::
+ ~CrashReportPrivateReportErrorFunction() = default;
+
+ExtensionFunction::ResponseAction CrashReportPrivateReportErrorFunction::Run() {
+ // Do not report errors if the user did not give consent for crash reporting.
+ if (!crash_reporter::GetClientCollectStatsConsent())
+ return RespondNow(NoArguments());
+
+ // Ensure we don't send too many crash reports. Limit to one report per hour.
+ if (!g_last_called_time.is_null() &&
+ g_clock->Now() - g_last_called_time < base::TimeDelta::FromHours(1)) {
+ return RespondNow(Error("Too many calls to this API"));
+ }
+
+ // TODO(https://crbug.com/986166): Use crash_reporter for Chrome OS.
+ const auto params = crash_report_private::ReportError::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ ReportJavaScriptError(
+ content::BrowserContext::GetDefaultStoragePartition(browser_context())
+ ->GetURLLoaderFactoryForBrowserProcess()
+ .get(),
+ params->info,
+ base::BindOnce(&CrashReportPrivateReportErrorFunction::OnReportComplete,
+ this));
+
+ g_last_called_time = base::Time::Now();
+
+ return RespondLater();
+}
+
+void CrashReportPrivateReportErrorFunction::OnReportComplete() {
+ Respond(NoArguments());
+}
+
+void SetClockForTesting(base::Clock* clock) {
+ g_clock = clock;
+}
+
+void SetCrashEndpointForTesting(const std::string& endpoint) {
+ GetCrashEndpoint() = endpoint;
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/extensions/browser/api/crash_report_private/crash_report_private_api.h b/extensions/browser/api/crash_report_private/crash_report_private_api.h
new file mode 100644
index 0000000..f91819f4
--- /dev/null
+++ b/extensions/browser/api/crash_report_private/crash_report_private_api.h
@@ -0,0 +1,43 @@
+// Copyright 2019 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 EXTENSIONS_BROWSER_API_CRASH_REPORT_PRIVATE_CRASH_REPORT_PRIVATE_API_H_
+#define EXTENSIONS_BROWSER_API_CRASH_REPORT_PRIVATE_CRASH_REPORT_PRIVATE_API_H_
+
+#include <string>
+
+#include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_function_histogram_value.h"
+
+namespace base {
+class Clock;
+}
+
+namespace extensions {
+namespace api {
+
+class CrashReportPrivateReportErrorFunction : public ExtensionFunction {
+ public:
+ CrashReportPrivateReportErrorFunction();
+ DECLARE_EXTENSION_FUNCTION("crashReportPrivate.reportError",
+ CRASHREPORTPRIVATE_REPORTERROR)
+
+ protected:
+ ~CrashReportPrivateReportErrorFunction() override;
+ ResponseAction Run() override;
+
+ private:
+ void OnReportComplete();
+
+ DISALLOW_COPY_AND_ASSIGN(CrashReportPrivateReportErrorFunction);
+};
+
+void SetClockForTesting(base::Clock* clock);
+
+void SetCrashEndpointForTesting(const std::string& endpoint);
+
+} // namespace api
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_CRASH_REPORT_PRIVATE_CRASH_REPORT_PRIVATE_API_H_
diff --git a/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc b/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc
new file mode 100644
index 0000000..72352b4e
--- /dev/null
+++ b/extensions/browser/api/crash_report_private/crash_report_private_apitest.cc
@@ -0,0 +1,233 @@
+// Copyright 2019 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 "base/system/sys_info.h"
+#include "base/test/simple_test_clock.h"
+#include "components/crash/content/app/crash_reporter_client.h"
+#include "content/public/test/browser_task_environment.h"
+#include "extensions/browser/api/crash_report_private/crash_report_private_api.h"
+#include "extensions/browser/browsertest_util.h"
+#include "extensions/common/switches.h"
+#include "extensions/shell/test/shell_apitest.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/test_extension_dir.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace extensions {
+
+using browsertest_util::ExecuteScriptInBackgroundPage;
+
+namespace {
+
+constexpr const char* kTestExtensionId = "jjeoclcdfjddkdjokiejckgcildcflpp";
+constexpr const char* kTestCrashEndpoint = "/crash";
+
+class MockCrashReporterClient : public crash_reporter::CrashReporterClient {
+ bool GetCollectStatsConsent() override { return true; }
+ void GetProductNameAndVersion(std::string* product_name,
+ std::string* version,
+ std::string* channel) override {
+ *product_name = "Chrome (Chrome OS)";
+ *version = "1.2.3.4";
+ *channel = "Stable";
+ }
+};
+
+std::string GetOsVersion() {
+ int32_t os_major_version = 0;
+ int32_t os_minor_version = 0;
+ int32_t os_bugfix_version = 0;
+ base::SysInfo::OperatingSystemVersionNumbers(
+ &os_major_version, &os_minor_version, &os_bugfix_version);
+ return base::StringPrintf("%d.%d.%d", os_major_version, os_minor_version,
+ os_bugfix_version);
+}
+
+} // namespace
+
+class CrashReportPrivateApiTest : public ShellApiTest {
+ public:
+ CrashReportPrivateApiTest() = default;
+ ~CrashReportPrivateApiTest() override = default;
+
+ void SetUpOnMainThread() override {
+ ShellApiTest::SetUpOnMainThread();
+
+ constexpr char kKey[] =
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+uU63MD6T82Ldq5wjrDFn5mGmPnnnj"
+ "WZBWxYXfpG4kVf0s+p24VkXwTXsxeI12bRm8/ft9sOq0XiLfgQEh5JrVUZqvFlaZYoS+g"
+ "iZfUqzKFGMLa4uiSMDnvv+byxrqAepKz5G8XX/q5Wm5cvpdjwgiu9z9iM768xJy+Ca/G5"
+ "qQwIDAQAB";
+ constexpr char kManifestTemplate[] =
+ R"({
+ "key": "%s",
+ "name": "chrome.crashReportPrivate basic extension tests",
+ "version": "1.0",
+ "manifest_version": 2,
+ "background": { "scripts": ["test.js"] },
+ "permissions": ["crashReportPrivate"]
+ })";
+
+ TestExtensionDir test_dir;
+ test_dir.WriteManifest(base::StringPrintf(kManifestTemplate, kKey));
+ test_dir.WriteFile(FILE_PATH_LITERAL("test.js"),
+ R"(chrome.test.sendMessage('ready');)");
+
+ ExtensionTestMessageListener listener("ready", false);
+ extension_ = LoadExtension(test_dir.UnpackedPath());
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ embedded_test_server()->RegisterRequestHandler(base::Bind(
+ &CrashReportPrivateApiTest::HandleRequest, base::Unretained(this)));
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ api::SetCrashEndpointForTesting(
+ embedded_test_server()->GetURL(kTestCrashEndpoint).spec());
+ crash_reporter::SetCrashReporterClient(&client_);
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitchASCII(
+ extensions::switches::kWhitelistedExtensionID, kTestExtensionId);
+ ShellApiTest::SetUpCommandLine(command_line);
+ }
+
+ protected:
+ struct Report {
+ std::string query;
+ std::string content;
+ };
+
+ const Extension* extension_;
+ Report last_report_;
+
+ private:
+ std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+ const net::test_server::HttpRequest& request) {
+ GURL absolute_url = embedded_test_server()->GetURL(request.relative_url);
+ if (absolute_url.path() != kTestCrashEndpoint) {
+ return nullptr;
+ }
+
+ last_report_ = {absolute_url.query(), request.content};
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("123");
+ http_response->set_content_type("text/plain");
+ return http_response;
+ }
+
+ MockCrashReporterClient client_;
+ DISALLOW_COPY_AND_ASSIGN(CrashReportPrivateApiTest);
+};
+
+IN_PROC_BROWSER_TEST_F(CrashReportPrivateApiTest, Basic) {
+ constexpr char kTestScript[] = R"(
+ chrome.crashReportPrivate.reportError({
+ message: "hi",
+ url: "http://www.test.com",
+ },
+ () => window.domAutomationController.send(""));
+ )";
+ ExecuteScriptInBackgroundPage(browser_context(), extension_->id(),
+ kTestScript);
+
+ EXPECT_EQ(last_report_.query,
+ "browser=Chrome&browser_version=1.2.3.4&channel=Stable&"
+ "full_url=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttp%2Fwww.test.com%2F&os=ChromeOS&os_version=" +
+ GetOsVersion() +
+ "&prod=Chrome%2520(Chrome%2520OS)&src=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttp%2Fwww.test."
+ "com%2F&type=JavascriptError&url=%2F&ver=1.2.3.4");
+ EXPECT_EQ(last_report_.content, "");
+}
+
+IN_PROC_BROWSER_TEST_F(CrashReportPrivateApiTest, ExtraParamsAndStackTrace) {
+ constexpr char kTestScript[] = R"(
+ chrome.crashReportPrivate.reportError({
+ message: "hi",
+ url: "http://www.test.com/foo",
+ product: "TestApp",
+ version: "1.0.0.0",
+ lineNumber: 123,
+ columnNumber: 456,
+ stackTrace: " at <anonymous>:1:1",
+ },
+ () => window.domAutomationController.send(""));
+ )";
+ ExecuteScriptInBackgroundPage(browser_context(), extension_->id(),
+ kTestScript);
+
+ EXPECT_EQ(last_report_.query,
+ "browser=Chrome&browser_version=1.2.3.4&channel=Stable&column=%C8&"
+ "full_url=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttp%2Fwww.test.com%2Ffoo&line=%7B&os=ChromeOS&"
+ "os_version=" +
+ GetOsVersion() +
+ "&prod=TestApp&src=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttp%2Fwww.test.com%2Ffoo&type="
+ "JavascriptError&url=%2Ffoo&ver=1.0.0.0");
+ EXPECT_EQ(last_report_.content, " at <anonymous>:1:1");
+}
+
+IN_PROC_BROWSER_TEST_F(CrashReportPrivateApiTest, StackTraceWithErrorMessage) {
+ constexpr char kTestScript[] = R"(
+ chrome.crashReportPrivate.reportError({
+ message: "hi",
+ url: "http://www.test.com/foo",
+ product: 'TestApp',
+ version: '1.0.0.0',
+ lineNumber: 123,
+ columnNumber: 456,
+ stackTrace: 'hi'
+ },
+ () => window.domAutomationController.send(""));
+ )";
+ ExecuteScriptInBackgroundPage(browser_context(), extension_->id(),
+ kTestScript);
+
+ EXPECT_EQ(last_report_.query,
+ "browser=Chrome&browser_version=1.2.3.4&channel=Stable&column=%C8&"
+ "full_url=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttp%2Fwww.test.com%2Ffoo&line=%7B&os=ChromeOS&"
+ "os_version=" +
+ GetOsVersion() +
+ "&prod=TestApp&src=https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttp%2Fwww.test.com%2Ffoo&type="
+ "JavascriptError&url=%2Ffoo&ver=1.0.0.0");
+ EXPECT_EQ(last_report_.content, "");
+}
+
+IN_PROC_BROWSER_TEST_F(CrashReportPrivateApiTest, Throttling) {
+ constexpr char kTestScript[] = R"(
+ chrome.crashReportPrivate.reportError({
+ message: "hi",
+ url: "http://www.test.com",
+ },
+ () => {
+ window.domAutomationController.send(chrome.runtime.lastError ?
+ chrome.runtime.lastError.message : "")
+ });
+ )";
+
+ base::SimpleTestClock test_clock;
+ test_clock.SetNow(base::Time::Now());
+ api::SetClockForTesting(&test_clock);
+
+ // Use an exact time for the first API call.
+ EXPECT_EQ("", ExecuteScriptInBackgroundPage(browser_context(),
+ extension_->id(), kTestScript));
+
+ // API is limited to one call per hr. So pretend the second call is just
+ // before 1 hr.
+ test_clock.Advance(base::TimeDelta::FromMinutes(59));
+ EXPECT_EQ("Too many calls to this API",
+ ExecuteScriptInBackgroundPage(browser_context(), extension_->id(),
+ kTestScript));
+
+ // Call again after 1 hr.
+ test_clock.Advance(base::TimeDelta::FromMinutes(2));
+ EXPECT_EQ("", ExecuteScriptInBackgroundPage(browser_context(),
+ extension_->id(), kTestScript));
+}
+
+} // namespace extensions
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 33318591..677c81c4 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1479,6 +1479,7 @@
TERMINALPRIVATE_SETSETTINGS = 1416,
WEBSTOREPRIVATE_REQUESTEXTENSION = 1417,
AUTOTESTPRIVATE_INSTALLPLUGINVM = 1418,
+ CRASHREPORTPRIVATE_REPORTERROR = 1419,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index a517a5f..d9c3b34 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -142,6 +142,10 @@
"clipboard.setImageData": {
"dependencies": ["permission:clipboardWrite"]
},
+ "crashReportPrivate": {
+ "dependencies": ["permission:crashReportPrivate"],
+ "contexts": ["blessed_extension"]
+ },
"declarativeNetRequest": {
"dependencies": ["permission:declarativeNetRequest"],
"contexts": ["blessed_extension"]
diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json
index 04a6c062..48f0789 100644
--- a/extensions/common/api/_permission_features.json
+++ b/extensions/common/api/_permission_features.json
@@ -199,6 +199,13 @@
"extension_types": ["platform_app"],
"platforms": ["chromeos"]
},
+ "crashReportPrivate": {
+ "channel": "dev",
+ "extension_types": ["extension"],
+ "whitelist": [
+ "06BE211D5F014BAB34BC22D9DDA09C63A81D828E" // http://crbug.com/946241
+ ]
+ },
"declarativeNetRequest": {
"channel": "beta",
"extension_types": ["extension"],
diff --git a/extensions/common/api/crash_report_private.idl b/extensions/common/api/crash_report_private.idl
new file mode 100644
index 0000000..9833e9b
--- /dev/null
+++ b/extensions/common/api/crash_report_private.idl
@@ -0,0 +1,48 @@
+// Copyright 2019 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.
+
+// Private API for Chrome component extensions to report errors.
+[platforms=("chromeos")]
+namespace crashReportPrivate {
+ // A dictionary containing additional context about the error.
+ dictionary ErrorInfo {
+ // The error message.
+ DOMString message;
+
+ // URL where the error occurred.
+ // Must be the full URL, containing the protocol (e.g.
+ // http://www.example.com).
+ DOMString url;
+
+ // Name of the product where the error occurred.
+ // Defaults to the product variant of Chrome that is hosting the extension.
+ // (e.g. "Chrome" or "Chrome_ChromeOS").
+ DOMString? product;
+
+ // Version of the product where the error occurred.
+ // Defaults to the version of Chrome that is hosting the extension (e.g.
+ // "73.0.3683.75").
+ DOMString? version;
+
+ // Line number where the error occurred.
+ long? lineNumber;
+
+ // Column number where the error occurred.
+ long? columnNumber;
+
+ // String containing the stack trace for the error.
+ // Defaults to the empty string.
+ DOMString? stackTrace;
+ };
+
+ // Callback for |reportError|.
+ callback ReportCallback = void ();
+
+ interface Functions {
+ // Report and upload an error to Crash.
+ // |info|: Information about the error.
+ // |callback|: Called when the error has been uploaded.
+ static void reportError(ErrorInfo info, ReportCallback callback);
+ };
+};
diff --git a/extensions/common/api/schema.gni b/extensions/common/api/schema.gni
index ac2a944..ac8db0f 100644
--- a/extensions/common/api/schema.gni
+++ b/extensions/common/api/schema.gni
@@ -63,6 +63,7 @@
if (is_chromeos) {
extensions_api_schema_files_ += [
+ "crash_report_private.idl",
"diagnostics.idl",
"lock_screen_data.idl",
"media_perception_private.idl",
diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h
index 911a31a..0b2c6ef8 100644
--- a/extensions/common/permissions/api_permission.h
+++ b/extensions/common/permissions/api_permission.h
@@ -266,6 +266,7 @@
kLoginState = 222,
kPrintingMetrics = 223,
kPrinting = 224,
+ kCrashReportPrivate = 225,
// Last entry: Add new entries above and ensure to update the
// "ExtensionPermission3" enum in tools/metrics/histograms/enums.xml
// (by running update_extension_permission.py).
diff --git a/extensions/common/permissions/extensions_api_permissions.cc b/extensions/common/permissions/extensions_api_permissions.cc
index 0a0ebfe..eeb5ca50 100644
--- a/extensions/common/permissions/extensions_api_permissions.cc
+++ b/extensions/common/permissions/extensions_api_permissions.cc
@@ -44,6 +44,7 @@
APIPermissionInfo::kFlagSupportsContentCapabilities},
{APIPermission::kClipboardWrite, "clipboardWrite",
APIPermissionInfo::kFlagSupportsContentCapabilities},
+ {APIPermission::kCrashReportPrivate, "crashReportPrivate"},
{APIPermission::kDeclarativeWebRequest, "declarativeWebRequest"},
{APIPermission::kDiagnostics, "diagnostics",
APIPermissionInfo::kFlagCannotBeOptional},
diff --git a/extensions/shell/test/shell_apitest.cc b/extensions/shell/test/shell_apitest.cc
index acbe697..e3d94a9 100644
--- a/extensions/shell/test/shell_apitest.cc
+++ b/extensions/shell/test/shell_apitest.cc
@@ -4,7 +4,6 @@
#include "extensions/shell/test/shell_apitest.h"
-#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/notification_service.h"
@@ -31,6 +30,11 @@
return extension_system_->LoadExtension(extension_path);
}
+const Extension* ShellApiTest::LoadExtension(
+ const base::FilePath& extension_path) {
+ return extension_system_->LoadExtension(extension_path);
+}
+
const Extension* ShellApiTest::LoadApp(const std::string& app_dir) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath test_data_dir;
diff --git a/extensions/shell/test/shell_apitest.h b/extensions/shell/test/shell_apitest.h
index 11ed054..8b0a00e 100644
--- a/extensions/shell/test/shell_apitest.h
+++ b/extensions/shell/test/shell_apitest.h
@@ -7,6 +7,7 @@
#include <string>
+#include "base/files/file_path.h"
#include "base/macros.h"
#include "extensions/shell/test/shell_test.h"
@@ -27,6 +28,11 @@
// |extension_dir| should be a subpath under extensions/test/data.
const Extension* LoadExtension(const std::string& extension_dir);
+ // Loads an unpacked extension. Returns an instance of the extension that was
+ // just loaded.
+ // |extension_path| should be an absolute path to the extension.
+ const Extension* LoadExtension(const base::FilePath& extension_path);
+
// Loads and launches an unpacked platform app. Returns an instance of the
// extension that was just loaded.
// |app_dir| should be a subpath under extensions/test/data.