summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/error_console/error_console_browsertest.cc353
-rw-r--r--chrome/browser/extensions/error_console/error_console_unittest.cc35
-rw-r--r--chrome/browser/extensions/extension_host.cc25
-rw-r--r--chrome/browser/extensions/extension_host.h6
-rw-r--r--chrome/browser/extensions/tab_helper.cc26
-rw-r--r--chrome/browser/extensions/tab_helper.h5
-rw-r--r--chrome/chrome_common.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/render_messages.h16
-rw-r--r--chrome/renderer/chrome_content_renderer_client.cc3
-rw-r--r--chrome/renderer/chrome_render_view_observer.cc87
-rw-r--r--chrome/renderer/chrome_render_view_observer.h6
-rw-r--r--chrome/test/data/extensions/error_console/browser_action_runtime_error/browser_action.js8
-rw-r--r--chrome/test/data/extensions/error_console/browser_action_runtime_error/manifest.json15
-rw-r--r--chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/content_script.js12
-rw-r--r--chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/manifest.json15
-rw-r--r--content/browser/renderer_host/render_view_host_delegate.cc2
-rw-r--r--content/browser/renderer_host/render_view_host_delegate.h3
-rw-r--r--content/browser/renderer_host/render_view_host_impl.cc8
-rw-r--r--content/browser/renderer_host/render_view_host_impl.h3
-rw-r--r--content/browser/web_contents/web_contents_impl.cc3
-rw-r--r--content/browser/web_contents/web_contents_impl.h3
-rw-r--r--content/common/view_messages.h5
-rw-r--r--content/public/renderer/render_view_observer.h12
-rw-r--r--content/renderer/render_view_impl.cc14
-rw-r--r--extensions/browser/extension_error.cc97
-rw-r--r--extensions/browser/extension_error.h45
-rw-r--r--extensions/common/DEPS (renamed from extensions/common/matcher/DEPS)0
-rw-r--r--extensions/common/constants.h6
-rw-r--r--extensions/common/extension_urls.cc16
-rw-r--r--extensions/common/extension_urls.h20
-rw-r--r--extensions/common/stack_frame.cc80
-rw-r--r--extensions/common/stack_frame.h41
-rw-r--r--extensions/common/stack_frame_unittest.cc85
34 files changed, 913 insertions, 147 deletions
diff --git a/chrome/browser/extensions/error_console/error_console_browsertest.cc b/chrome/browser/extensions/error_console/error_console_browsertest.cc
new file mode 100644
index 0000000..1cdbf62
--- /dev/null
+++ b/chrome/browser/extensions/error_console/error_console_browsertest.cc
@@ -0,0 +1,353 @@
+// 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.
+
+#include "chrome/browser/extensions/error_console/error_console.h"
+
+#include "base/prefs/pref_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_toolbar_model.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/feature_switch.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "extensions/browser/extension_error.h"
+#include "extensions/common/constants.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using base::string16;
+using base::UTF8ToUTF16;
+
+namespace extensions {
+
+namespace {
+
+const char kTestingPage[] = "/extensions/test_file.html";
+const char kAnonymousFunction[] = "(anonymous function)";
+const char* kBackgroundPageName =
+ extensions::kGeneratedBackgroundPageFilename;
+const int kNoFlags = 0;
+
+const StackTrace& GetStackTraceFromError(const ExtensionError* error) {
+ CHECK(error->type() == ExtensionError::RUNTIME_ERROR);
+ return (static_cast<const RuntimeError*>(error))->stack_trace();
+}
+
+// Verify that a given |frame| has the proper source and function name.
+void CheckStackFrame(const StackFrame& frame,
+ const std::string& source,
+ const std::string& function) {
+ EXPECT_EQ(UTF8ToUTF16(source), frame.source);
+ EXPECT_EQ(UTF8ToUTF16(function), frame.function);
+}
+
+// Verify that all properties of a given |frame| are correct. Overloaded because
+// we commonly do not check line/column numbers, as they are too likely
+// to change.
+void CheckStackFrame(const StackFrame& frame,
+ const std::string& source,
+ const std::string& function,
+ size_t line_number,
+ size_t column_number) {
+ CheckStackFrame(frame, source, function);
+ EXPECT_EQ(line_number, frame.line_number);
+ EXPECT_EQ(column_number, frame.column_number);
+}
+
+// Verify that all properties of a given |error| are correct.
+void CheckError(const ExtensionError* error,
+ ExtensionError::Type type,
+ const std::string& id,
+ const std::string& source,
+ bool from_incognito,
+ const std::string& message) {
+ ASSERT_TRUE(error);
+ EXPECT_EQ(type, error->type());
+ EXPECT_EQ(id, error->extension_id());
+ EXPECT_EQ(UTF8ToUTF16(source), error->source());
+ EXPECT_EQ(from_incognito, error->from_incognito());
+ EXPECT_EQ(UTF8ToUTF16(message), error->message());
+}
+
+// Verify that all properties of a JS runtime error are correct.
+void CheckRuntimeError(const ExtensionError* error,
+ const std::string& id,
+ const std::string& source,
+ bool from_incognito,
+ const std::string& message,
+ logging::LogSeverity level,
+ const GURL& context,
+ size_t expected_stack_size) {
+ CheckError(error,
+ ExtensionError::RUNTIME_ERROR,
+ id,
+ source,
+ from_incognito,
+ message);
+
+ const RuntimeError* runtime_error = static_cast<const RuntimeError*>(error);
+ EXPECT_EQ(level, runtime_error->level());
+ EXPECT_EQ(context, runtime_error->context_url());
+ EXPECT_EQ(expected_stack_size, runtime_error->stack_trace().size());
+}
+
+} // namespace
+
+class ErrorConsoleBrowserTest : public ExtensionBrowserTest {
+ public:
+ ErrorConsoleBrowserTest() : error_console_(NULL) { }
+ virtual ~ErrorConsoleBrowserTest() { }
+
+ protected:
+ // A helper class in order to wait for the proper number of errors to be
+ // caught by the ErrorConsole. This will run the MessageLoop until a given
+ // number of errors are observed.
+ // Usage:
+ // ...
+ // ErrorObserver observer(3, error_console);
+ // <Cause three errors...>
+ // observer.WaitForErrors();
+ // <Perform any additional checks...>
+ class ErrorObserver : public ErrorConsole::Observer {
+ public:
+ ErrorObserver(size_t errors_expected, ErrorConsole* error_console)
+ : errors_observed_(0),
+ errors_expected_(errors_expected),
+ waiting_(false),
+ error_console_(error_console) {
+ error_console_->AddObserver(this);
+ }
+ virtual ~ErrorObserver() {
+ if (error_console_)
+ error_console_->RemoveObserver(this);
+ }
+
+ // ErrorConsole::Observer implementation.
+ virtual void OnErrorAdded(const ExtensionError* error) OVERRIDE {
+ ++errors_observed_;
+ if (errors_observed_ >= errors_expected_) {
+ if (waiting_)
+ base::MessageLoopForUI::current()->Quit();
+ }
+ }
+
+ virtual void OnErrorConsoleDestroyed() OVERRIDE {
+ error_console_ = NULL;
+ }
+
+ // Spin until the appropriate number of errors have been observed.
+ void WaitForErrors() {
+ if (errors_observed_ < errors_expected_) {
+ waiting_ = true;
+ content::RunMessageLoop();
+ waiting_ = false;
+ }
+ }
+
+ private:
+ size_t errors_observed_;
+ size_t errors_expected_;
+ bool waiting_;
+
+ ErrorConsole* error_console_;
+
+ DISALLOW_COPY_AND_ASSIGN(ErrorObserver);
+ };
+
+ // The type of action which we take after we load an extension in order to
+ // cause any errors.
+ enum Action {
+ ACTION_NAVIGATE, // navigate to a page to allow a content script to run.
+ ACTION_BROWSER_ACTION, // simulate a browser action click.
+ ACTION_NONE // Do nothing (errors will be caused by a background script,
+ // or by a manifest/loading warning).
+ };
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
+
+ // We need to enable the ErrorConsole FeatureSwitch in order to collect
+ // errors.
+ FeatureSwitch::error_console()->SetOverrideValue(
+ FeatureSwitch::OVERRIDE_ENABLED);
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionBrowserTest::SetUpOnMainThread();
+
+ // Errors are only kept if we have Developer Mode enabled.
+ profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
+
+ error_console_ = ErrorConsole::Get(profile());
+ CHECK(error_console_);
+
+ test_data_dir_ = test_data_dir_.AppendASCII("error_console");
+ }
+
+ const GURL& GetTestURL() {
+ if (test_url_.is_empty()) {
+ CHECK(embedded_test_server()->InitializeAndWaitUntilReady());
+ test_url_ = embedded_test_server()->GetURL(kTestingPage);
+ }
+ return test_url_;
+ }
+
+ // Load the extension at |path|, take the specified |action|, and wait for
+ // |expected_errors| errors. Populate |extension| with a pointer to the loaded
+ // extension.
+ void LoadExtensionAndCheckErrors(
+ const std::string& path,
+ int flags,
+ size_t errors_expected,
+ Action action,
+ const Extension** extension) {
+ ErrorObserver observer(errors_expected, error_console_);
+ *extension =
+ LoadExtensionWithFlags(test_data_dir_.AppendASCII(path), flags);
+ ASSERT_TRUE(*extension);
+
+ switch (action) {
+ case ACTION_NAVIGATE: {
+ ui_test_utils::NavigateToURL(browser(), GetTestURL());
+ break;
+ }
+ case ACTION_BROWSER_ACTION: {
+ ExtensionService* service =
+ extensions::ExtensionSystem::Get(profile())->extension_service();
+ service->toolbar_model()->ExecuteBrowserAction(
+ *extension, browser(), NULL);
+ break;
+ }
+ case ACTION_NONE:
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ observer.WaitForErrors();
+
+ // We should only have errors for a single extension, or should have no
+ // entries, if no errors were expected.
+ ASSERT_EQ(errors_expected > 0 ? 1u : 0u, error_console()->errors().size());
+ ASSERT_EQ(
+ errors_expected,
+ error_console()->GetErrorsForExtension((*extension)->id()).size());
+ }
+
+ ErrorConsole* error_console() { return error_console_; }
+ private:
+ // The URL used in testing for simple page navigations.
+ GURL test_url_;
+
+ // Weak reference to the ErrorConsole.
+ ErrorConsole* error_console_;
+};
+
+// Load an extension which, upon visiting any page, first sends out a console
+// log, and then crashes with a JS TypeError.
+IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,
+ ContentScriptLogAndRuntimeError) {
+ const Extension* extension = NULL;
+ LoadExtensionAndCheckErrors(
+ "content_script_log_and_runtime_error",
+ kNoFlags,
+ 2u, // Two errors: A log message and a JS type error.
+ ACTION_NAVIGATE,
+ &extension);
+
+ std::string script_url = extension->url().Resolve("content_script.js").spec();
+
+ const ErrorConsole::ErrorList& errors =
+ error_console()->GetErrorsForExtension(extension->id());
+
+ // The first error should be a console log.
+ CheckRuntimeError(errors[0],
+ extension->id(),
+ script_url, // The source should be the content script url.
+ false, // Not from incognito.
+ "Hello, World!", // The error message is the log.
+ logging::LOG_INFO,
+ GetTestURL(), // Content scripts run in the web page.
+ 2u);
+
+ const StackTrace& stack_trace1 = GetStackTraceFromError(errors[0]);
+ CheckStackFrame(stack_trace1[0],
+ script_url,
+ "logHelloWorld", // function name
+ 6u, // line number
+ 11u /* column number */ );
+
+ CheckStackFrame(stack_trace1[1],
+ script_url,
+ kAnonymousFunction,
+ 9u,
+ 1u);
+
+ // The second error should be a runtime error.
+ CheckRuntimeError(errors[1],
+ extension->id(),
+ script_url,
+ false, // not from incognito
+ "Uncaught TypeError: "
+ "Cannot set property 'foo' of undefined",
+ logging::LOG_ERROR, // JS errors are always ERROR level.
+ GetTestURL(),
+ 1u);
+
+ const StackTrace& stack_trace2 = GetStackTraceFromError(errors[1]);
+ CheckStackFrame(stack_trace2[0],
+ script_url,
+ kAnonymousFunction,
+ 12u,
+ 1u);
+}
+
+// Catch an error from a BrowserAction; this is more complex than a content
+// script error, since browser actions are routed through our own code.
+IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BrowserActionRuntimeError) {
+ const Extension* extension = NULL;
+ LoadExtensionAndCheckErrors(
+ "browser_action_runtime_error",
+ kNoFlags,
+ 1u, // One error: A reference error from within the browser action.
+ ACTION_BROWSER_ACTION,
+ &extension);
+
+ std::string event_bindings_str = "event_bindings";
+ std::string script_url = extension->url().Resolve("browser_action.js").spec();
+
+ const ErrorConsole::ErrorList& errors =
+ error_console()->GetErrorsForExtension(extension->id());
+
+ CheckRuntimeError(
+ errors[0],
+ extension->id(),
+ script_url,
+ false, // not incognito
+ "Error in event handler for browserAction.onClicked: "
+ "ReferenceError: baz is not defined",
+ logging::LOG_ERROR,
+ extension->url().Resolve(kBackgroundPageName),
+ 6u);
+
+ const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
+
+ CheckStackFrame(stack_trace[0], script_url, kAnonymousFunction);
+ CheckStackFrame(stack_trace[1],
+ "extensions::SafeBuiltins",
+ std::string("Function.target.") + kAnonymousFunction);
+ CheckStackFrame(
+ stack_trace[2], event_bindings_str, "Event.dispatchToListener");
+ CheckStackFrame(stack_trace[3], event_bindings_str, "Event.dispatch_");
+ CheckStackFrame(stack_trace[4], event_bindings_str, "dispatchArgs");
+ CheckStackFrame(stack_trace[5], event_bindings_str, "dispatchEvent");
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/error_console/error_console_unittest.cc b/chrome/browser/extensions/error_console/error_console_unittest.cc
index e79c96f..dc880f9 100644
--- a/chrome/browser/extensions/error_console/error_console_unittest.cc
+++ b/chrome/browser/extensions/error_console/error_console_unittest.cc
@@ -20,28 +20,30 @@
#include "extensions/common/constants.h"
#include "extensions/common/id_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
using base::string16;
-using base::UTF8ToUTF16;
namespace extensions {
namespace {
-const char kExecutionContextURLKey[] = "executionContextURL";
-const char kStackTraceKey[] = "stackTrace";
+const char kDefaultStackTrace[] = "function_name (https://url.com:1:1)";
-string16 CreateErrorDetails(const std::string& extension_id) {
- base::DictionaryValue value;
- value.SetString(
- kExecutionContextURLKey,
+StackTrace GetDefaultStackTrace() {
+ StackTrace stack_trace;
+ scoped_ptr<StackFrame> frame =
+ StackFrame::CreateFromText(base::UTF8ToUTF16(kDefaultStackTrace));
+ CHECK(frame.get());
+ stack_trace.push_back(*frame);
+ return stack_trace;
+}
+
+string16 GetSourceForExtensionId(const std::string& extension_id) {
+ return base::UTF8ToUTF16(
std::string(kExtensionScheme) +
- content::kStandardSchemeSeparator +
- extension_id);
- value.Set(kStackTraceKey, new ListValue);
- std::string json_utf8;
- base::JSONWriter::Write(&value, &json_utf8);
- return UTF8ToUTF16(json_utf8);
+ content::kStandardSchemeSeparator +
+ extension_id);
}
scoped_ptr<ExtensionError> CreateNewRuntimeError(
@@ -50,10 +52,11 @@ scoped_ptr<ExtensionError> CreateNewRuntimeError(
const string16& message) {
return scoped_ptr<ExtensionError>(new RuntimeError(
from_incognito,
- UTF8ToUTF16("source"),
+ GetSourceForExtensionId(extension_id),
message,
- logging::LOG_INFO,
- CreateErrorDetails(extension_id)));
+ GetDefaultStackTrace(),
+ GURL::EmptyGURL(), // no context url
+ logging::LOG_INFO));
}
} // namespace
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc
index d7cd528..bd43928 100644
--- a/chrome/browser/extensions/extension_host.cc
+++ b/chrome/browser/extensions/extension_host.cc
@@ -7,6 +7,7 @@
#include <list>
#include "base/bind.h"
+#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
@@ -15,6 +16,7 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_shutdown.h"
#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/error_console/error_console.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_service.h"
@@ -48,7 +50,9 @@
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
+#include "extensions/browser/extension_error.h"
#include "extensions/browser/view_type_utils.h"
+#include "extensions/common/extension_urls.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
@@ -539,6 +543,8 @@ bool ExtensionHost::OnMessageReceived(const IPC::Message& message) {
OnIncrementLazyKeepaliveCount)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_DecrementLazyKeepaliveCount,
OnDecrementLazyKeepaliveCount)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
+ OnDetailedConsoleMessageAdded)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -568,6 +574,25 @@ void ExtensionHost::OnDecrementLazyKeepaliveCount() {
pm->DecrementLazyKeepaliveCount(extension());
}
+void ExtensionHost::OnDetailedConsoleMessageAdded(
+ const base::string16& message,
+ const base::string16& source,
+ const StackTrace& stack_trace,
+ int32 severity_level) {
+ if (IsSourceFromAnExtension(source)) {
+ ErrorConsole::Get(profile_)->ReportError(
+ scoped_ptr<ExtensionError>(new RuntimeError(
+ profile_->IsOffTheRecord(),
+ source,
+ message,
+ stack_trace,
+ associated_web_contents_ ?
+ associated_web_contents_->GetLastCommittedURL() :
+ GURL::EmptyGURL(),
+ static_cast<logging::LogSeverity>(severity_level))));
+ }
+}
+
void ExtensionHost::UnhandledKeyboardEvent(
WebContents* source,
const content::NativeWebKeyboardEvent& event) {
diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h
index fe97ef2..43f44f0 100644
--- a/chrome/browser/extensions/extension_host.h
+++ b/chrome/browser/extensions/extension_host.h
@@ -17,6 +17,7 @@
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
+#include "extensions/common/stack_frame.h"
#include "extensions/common/view_type.h"
#if defined(TOOLKIT_VIEWS)
@@ -221,6 +222,11 @@ class ExtensionHost : public content::WebContentsDelegate,
void OnEventAck();
void OnIncrementLazyKeepaliveCount();
void OnDecrementLazyKeepaliveCount();
+ void OnDetailedConsoleMessageAdded(
+ const base::string16& message,
+ const base::string16& source,
+ const StackTrace& stack_trace,
+ int32 severity_level);
// Handles keyboard events that were not handled by HandleKeyboardEvent().
// Platform specific implementation may override this method to handle the
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index ac7620c..3617718 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -4,11 +4,13 @@
#include "chrome/browser/extensions/tab_helper.h"
+#include "base/logging.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/activity_log/activity_log.h"
#include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/error_console/error_console.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_service.h"
@@ -33,6 +35,7 @@
#include "chrome/common/extensions/feature_switch.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
+#include "chrome/common/render_messages.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_details.h"
@@ -45,7 +48,9 @@
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
+#include "extensions/browser/extension_error.h"
#include "extensions/common/extension_resource.h"
+#include "extensions/common/extension_urls.h"
#include "ui/gfx/image/image.h"
using content::NavigationController;
@@ -237,6 +242,8 @@ bool TabHelper::OnMessageReceived(const IPC::Message& message) {
OnContentScriptsExecuting)
IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
OnWatchedPageChange)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
+ OnDetailedConsoleMessageAdded)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -253,7 +260,6 @@ void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
new_helper->extension_app_icon_ = extension_app_icon_;
}
-
void TabHelper::OnDidGetApplicationInfo(int32 page_id,
const WebApplicationInfo& info) {
// Android does not implement BrowserWindow.
@@ -350,6 +356,24 @@ void TabHelper::OnWatchedPageChange(
#endif // defined(ENABLE_EXTENSIONS)
}
+void TabHelper::OnDetailedConsoleMessageAdded(
+ const base::string16& message,
+ const base::string16& source,
+ const StackTrace& stack_trace,
+ int32 severity_level) {
+ if (IsSourceFromAnExtension(source)) {
+ ErrorConsole::Get(profile_)->ReportError(
+ scoped_ptr<ExtensionError>(new RuntimeError(
+ profile_->IsOffTheRecord(),
+ source,
+ message,
+ stack_trace,
+ web_contents() ?
+ web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
+ static_cast<logging::LogSeverity>(severity_level))));
+ }
+}
+
const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
if (extension_app_id.empty())
return NULL;
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index 95facc2..dfd7c94 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -19,6 +19,7 @@
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
+#include "extensions/common/stack_frame.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace content {
@@ -189,6 +190,10 @@ class TabHelper : public content::WebContentsObserver,
int32 page_id,
const GURL& on_url);
void OnWatchedPageChange(const std::vector<std::string>& css_selectors);
+ void OnDetailedConsoleMessageAdded(const base::string16& message,
+ const base::string16& source,
+ const StackTrace& stack_trace,
+ int32 severity_level);
// App extensions related methods:
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 157d2e6..310c4f8 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -72,6 +72,8 @@
'../extensions/common/extension_paths.h',
'../extensions/common/extension_resource.cc',
'../extensions/common/extension_resource.h',
+ '../extensions/common/extension_urls.cc',
+ '../extensions/common/extension_urls.h',
'../extensions/common/extensions_client.cc',
'../extensions/common/extensions_client.h',
'../extensions/common/features/feature.cc',
@@ -103,6 +105,8 @@
'../extensions/common/one_shot_event.cc',
'../extensions/common/one_shot_event.h',
'../extensions/common/permissions/permissions_provider.h',
+ '../extensions/common/stack_frame.cc',
+ '../extensions/common/stack_frame.h',
'../extensions/common/switches.cc',
'../extensions/common/switches.h',
'../extensions/common/url_pattern.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e1ceca4..e115b31 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1394,6 +1394,7 @@
'browser/extensions/cross_origin_xhr_apitest.cc',
'browser/extensions/crx_installer_browsertest.cc',
'browser/extensions/docs/examples/apps/calculator_browsertest.cc',
+ 'browser/extensions/error_console/error_console_browsertest.cc',
'browser/extensions/events_apitest.cc',
'browser/extensions/execute_script_apitest.cc',
'browser/extensions/extension_apitest.cc',
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index 8c8c881..7b04fce 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -30,6 +30,7 @@
#include "content/public/common/common_param_traits.h"
#include "content/public/common/referrer.h"
#include "content/public/common/top_controls_state.h"
+#include "extensions/common/stack_frame.h"
#include "ipc/ipc_channel_handle.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_platform_file.h"
@@ -235,6 +236,13 @@ IPC_STRUCT_TRAITS_BEGIN(LanguageDetectionDetails)
IPC_STRUCT_TRAITS_MEMBER(contents)
IPC_STRUCT_TRAITS_END()
+IPC_STRUCT_TRAITS_BEGIN(extensions::StackFrame)
+ IPC_STRUCT_TRAITS_MEMBER(line_number)
+ IPC_STRUCT_TRAITS_MEMBER(column_number)
+ IPC_STRUCT_TRAITS_MEMBER(source)
+ IPC_STRUCT_TRAITS_MEMBER(function)
+IPC_STRUCT_TRAITS_END()
+
IPC_ENUM_TRAITS_MAX_VALUE(NTPLoggingEventType,
NTP_NUM_EVENT_TYPES)
@@ -743,3 +751,11 @@ IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion,
IPC_MESSAGE_ROUTED2(ChromeViewHostMsg_SetVoiceSearchSupported,
int /* page_id */,
bool /* supported */)
+
+// Tells listeners that a detailed message was reported to the console by
+// WebKit.
+IPC_MESSAGE_ROUTED4(ChromeViewHostMsg_DetailedConsoleMessageAdded,
+ string16 /* message */,
+ string16 /* source */,
+ extensions::StackTrace /* stack trace */,
+ int32 /* severity level */)
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 1fa7a9c..ef9c3fc 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -71,6 +71,7 @@
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_visitor.h"
#include "extensions/common/constants.h"
+#include "extensions/common/extension_urls.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/renderer_resources.h"
@@ -1301,7 +1302,7 @@ bool ChromeContentRendererClient::AllowPepperMediaStreamAPI(
bool ChromeContentRendererClient::ShouldReportDetailedMessageForSource(
const base::string16& source) const {
- return GURL(source).SchemeIs(extensions::kExtensionScheme);
+ return extensions::IsSourceFromAnExtension(source);
}
} // namespace chrome
diff --git a/chrome/renderer/chrome_render_view_observer.cc b/chrome/renderer/chrome_render_view_observer.cc
index 4519910..c24cf29 100644
--- a/chrome/renderer/chrome_render_view_observer.cc
+++ b/chrome/renderer/chrome_render_view_observer.cc
@@ -10,6 +10,7 @@
#include "base/debug/trace_event.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
+#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
@@ -29,6 +30,7 @@
#include "content/public/renderer/content_renderer_client.h"
#include "content/public/renderer/render_view.h"
#include "extensions/common/constants.h"
+#include "extensions/common/stack_frame.h"
#include "net/base/data_url.h"
#include "skia/ext/image_operations.h"
#include "skia/ext/platform_canvas.h"
@@ -55,6 +57,8 @@
#include "v8/include/v8-testing.h"
#include "webkit/glue/webkit_glue.h"
+using base::string16;
+using extensions::APIPermission;
using WebKit::WebAccessibilityObject;
using WebKit::WebCString;
using WebKit::WebDataSource;
@@ -75,7 +79,6 @@ using WebKit::WebURLRequest;
using WebKit::WebView;
using WebKit::WebVector;
using WebKit::WebWindowFeatures;
-using extensions::APIPermission;
// Delay in milliseconds that we'll wait before capturing the page contents
// and thumbnail.
@@ -166,6 +169,7 @@ static bool isHostInDomain(const std::string& host, const std::string& domain) {
}
namespace {
+
GURL StripRef(const GURL& url) {
GURL::Replacements replacements;
replacements.ClearRef();
@@ -176,9 +180,9 @@ GURL StripRef(const GURL& url) {
// |thumbnail_min_area_pixels|, we return the image unmodified. Otherwise, we
// scale down the image so that the width and height do not exceed
// |thumbnail_max_size_pixels|, preserving the original aspect ratio.
-static SkBitmap Downscale(WebKit::WebImage image,
- int thumbnail_min_area_pixels,
- gfx::Size thumbnail_max_size_pixels) {
+SkBitmap Downscale(WebKit::WebImage image,
+ int thumbnail_min_area_pixels,
+ gfx::Size thumbnail_max_size_pixels) {
if (image.isNull())
return SkBitmap();
@@ -207,6 +211,62 @@ static SkBitmap Downscale(WebKit::WebImage image,
static_cast<int>(scaled_size.width()),
static_cast<int>(scaled_size.height()));
}
+
+// The delimiter for a stack trace provided by WebKit.
+const char kStackFrameDelimiter[] = "\n at ";
+
+// Get a stack trace from a WebKit console message.
+// There are three possible scenarios:
+// 1. WebKit gives us a stack trace in |stack_trace|.
+// 2. The stack trace is embedded in the error |message| by an internal
+// script. This will be more useful than |stack_trace|, since |stack_trace|
+// will include the internal bindings trace, instead of a developer's code.
+// 3. No stack trace is included. In this case, we should mock one up from
+// the given line number and source.
+// |message| will be populated with the error message only (i.e., will not
+// include any stack trace).
+extensions::StackTrace GetStackTraceFromMessage(string16* message,
+ const string16& source,
+ const string16& stack_trace,
+ int32 line_number) {
+ extensions::StackTrace result;
+ std::vector<base::string16> pieces;
+ size_t index = 0;
+
+ if (message->find(base::UTF8ToUTF16(kStackFrameDelimiter)) !=
+ string16::npos) {
+ base::SplitStringUsingSubstr(*message,
+ base::UTF8ToUTF16(kStackFrameDelimiter),
+ &pieces);
+ *message = pieces[0];
+ index = 1;
+ } else if (!stack_trace.empty()) {
+ base::SplitStringUsingSubstr(stack_trace,
+ base::UTF8ToUTF16(kStackFrameDelimiter),
+ &pieces);
+ }
+
+ // If we got a stack trace, parse each frame from the text.
+ if (index < pieces.size()) {
+ for (; index < pieces.size(); ++index) {
+ scoped_ptr<extensions::StackFrame> frame =
+ extensions::StackFrame::CreateFromText(pieces[index]);
+ if (frame.get())
+ result.push_back(*frame);
+ }
+ }
+
+ if (result.empty()) { // If we don't have a stack trace, mock one up.
+ result.push_back(
+ extensions::StackFrame(line_number,
+ 1u, // column number
+ source,
+ EmptyString16() /* no function name */ ));
+ }
+
+ return result;
+}
+
} // namespace
ChromeRenderViewObserver::ChromeRenderViewObserver(
@@ -767,6 +827,25 @@ void ChromeRenderViewObserver::DidHandleGestureEvent(
text_input_type != WebKit::WebTextInputTypeNone));
}
+void ChromeRenderViewObserver::DetailedConsoleMessageAdded(
+ const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace_string,
+ int32 line_number,
+ int32 severity_level) {
+ string16 trimmed_message = message;
+ extensions::StackTrace stack_trace = GetStackTraceFromMessage(
+ &trimmed_message,
+ source,
+ stack_trace_string,
+ line_number);
+ Send(new ChromeViewHostMsg_DetailedConsoleMessageAdded(routing_id(),
+ trimmed_message,
+ source,
+ stack_trace,
+ severity_level));
+}
+
void ChromeRenderViewObserver::CapturePageInfoLater(int page_id,
bool preliminary_capture,
base::TimeDelta delay) {
diff --git a/chrome/renderer/chrome_render_view_observer.h b/chrome/renderer/chrome_render_view_observer.h
index 67dd03e..b556e46 100644
--- a/chrome/renderer/chrome_render_view_observer.h
+++ b/chrome/renderer/chrome_render_view_observer.h
@@ -9,6 +9,7 @@
#include <string>
#include <vector>
+#include "base/basictypes.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/scoped_ptr.h"
#include "base/timer/timer.h"
@@ -73,6 +74,11 @@ class ChromeRenderViewObserver : public content::RenderViewObserver,
virtual void DidClearWindowObject(WebKit::WebFrame* frame) OVERRIDE;
virtual void DidHandleGestureEvent(
const WebKit::WebGestureEvent& event) OVERRIDE;
+ virtual void DetailedConsoleMessageAdded(const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace,
+ int32 line_number,
+ int32 severity_level) OVERRIDE;
// WebKit::WebPermissionClient implementation.
virtual bool allowDatabase(WebKit::WebFrame* frame,
diff --git a/chrome/test/data/extensions/error_console/browser_action_runtime_error/browser_action.js b/chrome/test/data/extensions/error_console/browser_action_runtime_error/browser_action.js
new file mode 100644
index 0000000..8921354
--- /dev/null
+++ b/chrome/test/data/extensions/error_console/browser_action_runtime_error/browser_action.js
@@ -0,0 +1,8 @@
+// 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.
+
+chrome.browserAction.onClicked.addListener(function(tab) {
+ // This will cause a javascript error, since these are not defined.
+ foobar = baz;
+});
diff --git a/chrome/test/data/extensions/error_console/browser_action_runtime_error/manifest.json b/chrome/test/data/extensions/error_console/browser_action_runtime_error/manifest.json
new file mode 100644
index 0000000..78a19a2
--- /dev/null
+++ b/chrome/test/data/extensions/error_console/browser_action_runtime_error/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "Browser Action Runtime Error",
+ "description": "An extension which causes a JS runtime error when the browser action is triggered.",
+ "version": "1.0",
+ "manifest_version": 2,
+ "background": {
+ "scripts": ["browser_action.js"]
+ },
+ "permissions": [
+ "tabs", "*://*/*"
+ ],
+ "browser_action": {
+ "name": "CauseError"
+ }
+}
diff --git a/chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/content_script.js b/chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/content_script.js
new file mode 100644
index 0000000..ae581fb
--- /dev/null
+++ b/chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/content_script.js
@@ -0,0 +1,12 @@
+// 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.
+
+function logHelloWorld() {
+ console.log("Hello, World!");
+}
+
+logHelloWorld();
+
+var bar = undefined;
+bar.foo = 'baz';
diff --git a/chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/manifest.json b/chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/manifest.json
new file mode 100644
index 0000000..60fdd2a
--- /dev/null
+++ b/chrome/test/data/extensions/error_console/content_script_log_and_runtime_error/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "Content Script Log and Runtime Error",
+ "description": "An extension with a context script which logs a simple message and then causes a runtime type error.",
+ "version": "2.0",
+ "permissions": [
+ "tabs"
+ ],
+ "content_scripts": [
+ {
+ "matches": ["*://*/*"],
+ "js": ["content_script.js"]
+ }
+ ],
+ "manifest_version": 2
+}
diff --git a/content/browser/renderer_host/render_view_host_delegate.cc b/content/browser/renderer_host/render_view_host_delegate.cc
index 7bc3753..5ef5dc0 100644
--- a/content/browser/renderer_host/render_view_host_delegate.cc
+++ b/content/browser/renderer_host/render_view_host_delegate.cc
@@ -25,7 +25,7 @@ bool RenderViewHostDelegate::OnMessageReceived(RenderViewHost* render_view_host,
bool RenderViewHostDelegate::AddMessageToConsole(
int32 level, const string16& message, int32 line_no,
- const string16& source_id, const string16& stack_trace) {
+ const string16& source_id) {
return false;
}
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index 5cb5172..c19f8d2 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -275,8 +275,7 @@ class CONTENT_EXPORT RenderViewHostDelegate {
virtual bool AddMessageToConsole(int32 level,
const string16& message,
int32 line_no,
- const string16& source_id,
- const string16& stack_trace);
+ const string16& source_id);
// Return a dummy RendererPreferences object that will be used by the renderer
// associated with the owning RenderViewHost.
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index de33517..73a655c 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -1528,12 +1528,10 @@ void RenderViewHostImpl::OnAddMessageToConsole(
int32 level,
const string16& message,
int32 line_no,
- const string16& source_id,
- const string16& stack_trace) {
- if (delegate_->AddMessageToConsole(
- level, message, line_no, source_id, stack_trace)) {
+ const string16& source_id) {
+ if (delegate_->AddMessageToConsole(level, message, line_no, source_id))
return;
- }
+
// Pass through log level only on WebUI pages to limit console spew.
int32 resolved_level = HasWebUIScheme(delegate_->GetURL()) ? level : 0;
diff --git a/content/browser/renderer_host/render_view_host_impl.h b/content/browser/renderer_host/render_view_host_impl.h
index 880a339..64a0f8e 100644
--- a/content/browser/renderer_host/render_view_host_impl.h
+++ b/content/browser/renderer_host/render_view_host_impl.h
@@ -552,8 +552,7 @@ class CONTENT_EXPORT RenderViewHostImpl
void OnAddMessageToConsole(int32 level,
const string16& message,
int32 line_no,
- const string16& source_id,
- const string16& stack_trace);
+ const string16& source_id);
void OnUpdateInspectorSetting(const std::string& key,
const std::string& value);
void OnShouldCloseACK(
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 4a4d406..2d3c542 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3426,8 +3426,7 @@ void WebContentsImpl::RunBeforeUnloadConfirm(RenderViewHost* rvh,
bool WebContentsImpl::AddMessageToConsole(int32 level,
const string16& message,
int32 line_no,
- const string16& source_id,
- const string16& stack_trace) {
+ const string16& source_id) {
if (!delegate_)
return false;
return delegate_->AddMessageToConsole(this, level, message, line_no,
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index bd896fe..8fead69 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -408,8 +408,7 @@ class CONTENT_EXPORT WebContentsImpl
virtual bool AddMessageToConsole(int32 level,
const string16& message,
int32 line_no,
- const string16& source_id,
- const string16& stack_trace) OVERRIDE;
+ const string16& source_id) OVERRIDE;
virtual RendererPreferences GetRendererPrefs(
BrowserContext* browser_context) const OVERRIDE;
virtual WebPreferences GetWebkitPrefs() OVERRIDE;
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 6273f84..030f899 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -2015,12 +2015,11 @@ IPC_MESSAGE_ROUTED0(ViewHostMsg_ImeCancelComposition)
// WebKit and JavaScript error messages to log to the console
// or debugger UI.
-IPC_MESSAGE_ROUTED5(ViewHostMsg_AddMessageToConsole,
+IPC_MESSAGE_ROUTED4(ViewHostMsg_AddMessageToConsole,
int32, /* log level */
string16, /* msg */
int32, /* line number */
- string16, /* source id */
- string16 /* stack trace */ )
+ string16 /* source id */ )
// Sent by the renderer process to indicate that a plugin instance has crashed.
// Note: |plugin_pid| should not be trusted. The corresponding process has
diff --git a/content/public/renderer/render_view_observer.h b/content/public/renderer/render_view_observer.h
index 10dad33..164c370 100644
--- a/content/public/renderer/render_view_observer.h
+++ b/content/public/renderer/render_view_observer.h
@@ -91,6 +91,18 @@ class CONTENT_EXPORT RenderViewObserver : public IPC::Listener,
virtual void DidHandleGestureEvent(const WebKit::WebGestureEvent& event) {}
virtual void DidCreatePepperPlugin(RendererPpapiHost* host) {}
+ // Called when we receive a console message from WebKit for which we requested
+ // extra details (like the stack trace). |message| is the error message,
+ // |source| is the WebKit-reported source of the error (either external or
+ // internal), and |stack_trace| is the stack trace of the error in a
+ // human-readable format (each frame is formatted as
+ // "\n at function_name (source:line_number:column_number)").
+ virtual void DetailedConsoleMessageAdded(const base::string16& message,
+ const base::string16& source,
+ const base::string16& stack_trace,
+ int32 line_number,
+ int32 severity_level) {}
+
// These match incoming IPCs.
virtual void Navigate(const GURL& url) {}
virtual void ClosePage() {}
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 4d450a2..3002d78 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -2414,12 +2414,22 @@ void RenderViewImpl::didAddMessageToConsole(
NOTREACHED();
}
+ if (shouldReportDetailedMessageForSource(source_name)) {
+ FOR_EACH_OBSERVER(
+ RenderViewObserver,
+ observers_,
+ DetailedConsoleMessageAdded(message.text,
+ source_name,
+ stack_trace,
+ source_line,
+ static_cast<int32>(log_severity)));
+ }
+
Send(new ViewHostMsg_AddMessageToConsole(routing_id_,
static_cast<int32>(log_severity),
message.text,
static_cast<int32>(source_line),
- source_name,
- stack_trace));
+ source_name));
}
void RenderViewImpl::printPage(WebFrame* frame) {
diff --git a/extensions/browser/extension_error.cc b/extensions/browser/extension_error.cc
index eaef221..9b1e95f 100644
--- a/extensions/browser/extension_error.cc
+++ b/extensions/browser/extension_error.cc
@@ -4,7 +4,6 @@
#include "extensions/browser/extension_error.h"
-#include "base/json/json_reader.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
@@ -97,42 +96,21 @@ bool ManifestError::IsEqualImpl(const ExtensionError* rhs) const {
return true;
}
-RuntimeError::StackFrame::StackFrame() : line_number(-1), column_number(-1) {
-}
-
-RuntimeError::StackFrame::StackFrame(size_t frame_line,
- size_t frame_column,
- const string16& frame_url,
- const string16& frame_function)
- : line_number(frame_line),
- column_number(frame_column),
- url(frame_url),
- function(frame_function) {
-}
-
-RuntimeError::StackFrame::~StackFrame() {
-}
-
-bool RuntimeError::StackFrame::operator==(
- const RuntimeError::StackFrame& rhs) const {
- return line_number == rhs.line_number &&
- column_number == rhs.column_number &&
- url == rhs.url &&
- function == rhs.function;
-}
RuntimeError::RuntimeError(bool from_incognito,
const string16& source,
const string16& message,
- logging::LogSeverity level,
- const string16& details)
+ const StackTrace& stack_trace,
+ const GURL& context_url,
+ logging::LogSeverity level)
: ExtensionError(ExtensionError::RUNTIME_ERROR,
- std::string(), // We don't know the id yet.
+ GURL(source).host(),
from_incognito,
level,
source,
- message) {
- ParseDetails(details);
- DetermineExtensionID();
+ message),
+ context_url_(context_url),
+ stack_trace_(stack_trace) {
+ CleanUpInit();
}
RuntimeError::~RuntimeError() {
@@ -141,14 +119,14 @@ RuntimeError::~RuntimeError() {
std::string RuntimeError::PrintForTest() const {
std::string result = ExtensionError::PrintForTest() +
"\n Type: RuntimeError"
- "\n Context: " + base::UTF16ToUTF8(execution_context_url_) +
+ "\n Context: " + context_url_.spec() +
"\n Stack Trace: ";
for (StackTrace::const_iterator iter = stack_trace_.begin();
iter != stack_trace_.end(); ++iter) {
result += "\n {"
"\n Line: " + base::IntToString(iter->line_number) +
"\n Column: " + base::IntToString(iter->column_number) +
- "\n URL: " + base::UTF16ToUTF8(iter->url) +
+ "\n URL: " + base::UTF16ToUTF8(iter->source) +
"\n Function: " + base::UTF16ToUTF8(iter->function) +
"\n }";
}
@@ -163,49 +141,30 @@ bool RuntimeError::IsEqualImpl(const ExtensionError* rhs) const {
// of displaying an old and inaccurate stack trace.
return level_ == level_ &&
source_ == source_ &&
- execution_context_url_ == error->execution_context_url_ &&
+ context_url_ == error->context_url_ &&
stack_trace_.size() == error->stack_trace_.size() &&
(stack_trace_.empty() || stack_trace_[0] == error->stack_trace_[0]);
}
-void RuntimeError::ParseDetails(const string16& details) {
- scoped_ptr<base::Value> value(
- base::JSONReader::Read(base::UTF16ToUTF8(details)));
- const base::DictionaryValue* details_value;
- const base::ListValue* trace_value = NULL;
-
- // The |details| value should contain an execution context url and a stack
- // trace.
- if (!value.get() ||
- !value->GetAsDictionary(&details_value) ||
- !details_value->GetString(kExecutionContextURLKey,
- &execution_context_url_) ||
- !details_value->GetList(kStackTraceKey, &trace_value)) {
- NOTREACHED();
- return;
+void RuntimeError::CleanUpInit() {
+ // If the error came from a generated background page, the "context" is empty
+ // because there's no visible URL. We should set context to be the generated
+ // background page in this case.
+ GURL source_url = GURL(source_);
+ if (context_url_.is_empty() &&
+ source_url.path() ==
+ std::string("/") + kGeneratedBackgroundPageFilename) {
+ context_url_ = source_url;
}
- int line = 0;
- int column = 0;
- string16 url;
-
- for (size_t i = 0; i < trace_value->GetSize(); ++i) {
- const base::DictionaryValue* frame_value = NULL;
- CHECK(trace_value->GetDictionary(i, &frame_value));
-
- frame_value->GetInteger(kLineNumberKey, &line);
- frame_value->GetInteger(kColumnNumberKey, &column);
- frame_value->GetString(kURLKey, &url);
-
- string16 function;
- frame_value->GetString(kFunctionNameKey, &function); // This can be empty.
- stack_trace_.push_back(StackFrame(line, column, url, function));
- }
-}
-
-void RuntimeError::DetermineExtensionID() {
- if (!GetExtensionIDFromGURL(GURL(source_), &extension_id_))
- GetExtensionIDFromGURL(GURL(execution_context_url_), &extension_id_);
+ // In some instances (due to the fact that we're reusing error reporting from
+ // other systems), the source won't match up with the final entry in the stack
+ // trace. (For instance, in a browser action error, the source is the page -
+ // sometimes the background page - but the error is thrown from the script.)
+ // Make the source match the stack trace, since that is more likely the cause
+ // of the error.
+ if (!stack_trace_.empty() && source_ != stack_trace_[0].source)
+ source_ = stack_trace_[0].source;
}
} // namespace extensions
diff --git a/extensions/browser/extension_error.h b/extensions/browser/extension_error.h
index c5be169..6ee7791 100644
--- a/extensions/browser/extension_error.h
+++ b/extensions/browser/extension_error.h
@@ -11,6 +11,8 @@
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/strings/string16.h"
+#include "extensions/common/stack_frame.h"
+#include "url/gurl.h"
namespace extensions {
@@ -83,52 +85,27 @@ class ManifestError : public ExtensionError {
class RuntimeError : public ExtensionError {
public:
- struct StackFrame {
- size_t line_number;
- size_t column_number;
- // This is stored as a string (rather than a url) since it can be a
- // Chrome script file (e.g., event_bindings.js).
- base::string16 url;
- base::string16 function; // optional
-
- // STL-Required constructor
- StackFrame();
-
- StackFrame(size_t frame_line,
- size_t frame_column,
- const base::string16& frame_url,
- const base::string16& frame_function /* can be empty */);
-
- ~StackFrame();
-
- bool operator==(const StackFrame& rhs) const;
- };
- typedef std::vector<StackFrame> StackTrace;
-
RuntimeError(bool from_incognito,
const base::string16& source,
const base::string16& message,
- logging::LogSeverity level,
- const base::string16& details);
+ const StackTrace& stack_trace,
+ const GURL& context_url,
+ logging::LogSeverity level);
virtual ~RuntimeError();
virtual std::string PrintForTest() const OVERRIDE;
- const base::string16& execution_context_url() const {
- return execution_context_url_;
- }
+ const GURL& context_url() const { return context_url_; }
const StackTrace& stack_trace() const { return stack_trace_; }
private:
virtual bool IsEqualImpl(const ExtensionError* rhs) const OVERRIDE;
- // Parse the JSON |details| passed to the error. This includes a stack trace
- // and an execution context url.
- void ParseDetails(const base::string16& details);
- // Try to determine the ID of the extension. This may be obtained through the
- // reported source, or through the execution context url.
- void DetermineExtensionID();
+ // Since we piggy-back onto other error reporting systems (like V8 and
+ // WebKit), the reported information may need to be cleaned up in order to be
+ // in a consistent format.
+ void CleanUpInit();
- base::string16 execution_context_url_;
+ GURL context_url_;
StackTrace stack_trace_;
DISALLOW_COPY_AND_ASSIGN(RuntimeError);
diff --git a/extensions/common/matcher/DEPS b/extensions/common/DEPS
index 972b087..972b087 100644
--- a/extensions/common/matcher/DEPS
+++ b/extensions/common/DEPS
diff --git a/extensions/common/constants.h b/extensions/common/constants.h
index 492ddbc..648663d 100644
--- a/extensions/common/constants.h
+++ b/extensions/common/constants.h
@@ -12,13 +12,13 @@ namespace extensions {
// Scheme we serve extension content from.
extern const char kExtensionScheme[];
- // The name of the manifest inside an extension.
+// The name of the manifest inside an extension.
extern const base::FilePath::CharType kManifestFilename[];
- // The name of locale folder inside an extension.
+// The name of locale folder inside an extension.
extern const base::FilePath::CharType kLocaleFolder[];
- // The name of the messages file inside an extension.
+// The name of the messages file inside an extension.
extern const base::FilePath::CharType kMessagesFilename[];
// The base directory for subdirectories with platform-specific code.
diff --git a/extensions/common/extension_urls.cc b/extensions/common/extension_urls.cc
new file mode 100644
index 0000000..f354b6b
--- /dev/null
+++ b/extensions/common/extension_urls.cc
@@ -0,0 +1,16 @@
+// 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.
+
+#include "extensions/common/extension_urls.h"
+
+#include "extensions/common/constants.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+bool IsSourceFromAnExtension(const base::string16& source) {
+ return GURL(source).SchemeIs(kExtensionScheme);
+}
+
+} // namespace extensions
diff --git a/extensions/common/extension_urls.h b/extensions/common/extension_urls.h
new file mode 100644
index 0000000..7fa7e1e
--- /dev/null
+++ b/extensions/common/extension_urls.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef EXTENSIONS_COMMON_EXTENSION_URLS_H_
+#define EXTENSIONS_COMMON_EXTENSION_URLS_H_
+
+#include "base/strings/string16.h"
+
+namespace extensions {
+
+// Determine whether or not a source came from an extension. |source| can link
+// to a page or a script, and can be external (e.g., "http://www.google.com"),
+// extension-related (e.g., "chrome-extension://<extension_id>/background.js"),
+// or internal (e.g., "event_bindings" or "schemaUtils").
+bool IsSourceFromAnExtension(const base::string16& source);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_EXTENSION_URLS_H_
diff --git a/extensions/common/stack_frame.cc b/extensions/common/stack_frame.cc
new file mode 100644
index 0000000..d287496
--- /dev/null
+++ b/extensions/common/stack_frame.cc
@@ -0,0 +1,80 @@
+// 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.
+
+#include "extensions/common/stack_frame.h"
+
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/re2/re2/re2.h"
+
+namespace extensions {
+
+namespace {
+const char kAnonymousFunction[] = "(anonymous function)";
+}
+
+StackFrame::StackFrame() : line_number(1), column_number(1) {
+}
+
+StackFrame::StackFrame(const StackFrame& frame)
+ : line_number(frame.line_number),
+ column_number(frame.column_number),
+ source(frame.source),
+ function(frame.function) {
+}
+
+StackFrame::StackFrame(size_t line_number,
+ size_t column_number,
+ const base::string16& source,
+ const base::string16& function)
+ : line_number(line_number),
+ column_number(column_number),
+ source(source),
+ function(function.empty() ? base::UTF8ToUTF16(kAnonymousFunction)
+ : function) {
+}
+
+StackFrame::~StackFrame() {
+}
+
+// Create a stack frame from the passed text. The text must follow one of two
+// formats:
+// - "function_name (source:line_number:column_number)"
+// - "source:line_number:column_number"
+// (We have to recognize two formats because V8 will report stack traces in
+// both ways. If we reconcile this, we can clean this up.)
+// static
+scoped_ptr<StackFrame> StackFrame::CreateFromText(
+ const base::string16& frame_text) {
+ // We need to use utf8 for re2 matching.
+ std::string text = base::UTF16ToUTF8(frame_text);
+
+ size_t line = 1;
+ size_t column = 1;
+ std::string source;
+ std::string function;
+ if (!re2::RE2::FullMatch(text,
+ "(.+) \\(([^\\(\\)]+):(\\d+):(\\d+)\\)",
+ &function, &source, &line, &column) &&
+ !re2::RE2::FullMatch(text,
+ "([^\\(\\)]+):(\\d+):(\\d+)",
+ &source, &line, &column)) {
+ return scoped_ptr<StackFrame>();
+ }
+
+ return scoped_ptr<StackFrame>(new StackFrame(line,
+ column,
+ base::UTF8ToUTF16(source),
+ base::UTF8ToUTF16(function)));
+}
+
+bool StackFrame::operator==(const StackFrame& rhs) const {
+ return line_number == rhs.line_number &&
+ column_number == rhs.column_number &&
+ source == rhs.source &&
+ function == rhs.function;
+}
+
+} // namespace extensions
diff --git a/extensions/common/stack_frame.h b/extensions/common/stack_frame.h
new file mode 100644
index 0000000..7052605
--- /dev/null
+++ b/extensions/common/stack_frame.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef EXTENSIONS_COMMON_STACK_FRAME
+#define EXTENSIONS_COMMON_STACK_FRAME
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+
+namespace extensions {
+
+struct StackFrame {
+ StackFrame();
+ StackFrame(const StackFrame& frame);
+ StackFrame(size_t line_number,
+ size_t column_number,
+ const base::string16& source,
+ const base::string16& function);
+ ~StackFrame();
+
+ // Construct a stack frame from a reported plain-text frame.
+ static scoped_ptr<StackFrame> CreateFromText(
+ const base::string16& frame_text);
+
+ bool operator==(const StackFrame& rhs) const;
+
+ size_t line_number;
+ size_t column_number;
+ base::string16 source;
+ base::string16 function; // optional
+};
+
+typedef std::vector<StackFrame> StackTrace;
+
+} // namespace extensions
+
+#endif // EXTENSIONS_COMMON_STACK_FRAME
+
diff --git a/extensions/common/stack_frame_unittest.cc b/extensions/common/stack_frame_unittest.cc
new file mode 100644
index 0000000..7dad047
--- /dev/null
+++ b/extensions/common/stack_frame_unittest.cc
@@ -0,0 +1,85 @@
+// 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.
+
+#include "extensions/common/stack_frame.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::UTF8ToUTF16;
+
+namespace extensions {
+
+namespace {
+
+void AssertStackFrameValid(const std::string& text,
+ size_t line,
+ size_t column,
+ const std::string& source,
+ const std::string& function) {
+ base::string16 utf16_text = UTF8ToUTF16(text);
+ scoped_ptr<StackFrame> frame = StackFrame::CreateFromText(utf16_text);
+
+ ASSERT_TRUE(frame.get()) << "Failed to create frame from '" << text << "'";
+ EXPECT_EQ(line, frame->line_number());
+ EXPECT_EQ(column, frame->column_number());
+ EXPECT_EQ(UTF8ToUTF16(source), frame->source());
+ EXPECT_EQ(UTF8ToUTF16(function), frame->function());
+}
+
+void AssertStackFrameInvalid(const std::string& text) {
+ base::string16 utf16_text = UTF8ToUTF16(text);
+ scoped_ptr<StackFrame> frame = StackFrame::CreateFromText(utf16_text);
+ ASSERT_FALSE(frame.get()) << "Errantly created frame from '" << text << "'";
+}
+
+} // namespace
+
+TEST(StackFrameUnitTest, ParseStackFramesFromText) {
+ AssertStackFrameValid(
+ "function_name (https://www.url.com/foo.html:100:201)",
+ 100u, 201u, "https://www.url.com/foo.html", "function_name");
+ AssertStackFrameValid(
+ "(anonymous function) (https://www.url.com/foo.html:100:201)",
+ 100u, 201u, "https://www.url.com/foo.html", "(anonymous function)");
+ AssertStackFrameValid(
+ "Function.target.(anonymous function) (internals::SafeBuiltins:19:14)",
+ 19u, 14u, "internals::SafeBuiltins",
+ "Function.target.(anonymous function)");
+ AssertStackFrameValid(
+ "internal-item:://fpgohbggpmcpeedljibghijiclejiklo/script.js:6:12",
+ 6u, 12u, "internal-item:://fpgohbggpmcpeedljibghijiclejiklo/script.js",
+ "(anonymous function)");
+
+ // No delimiting ':' between line/column numbers.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100201)");
+ // No line number.
+ AssertStackFrameInvalid("function_name (https://www.url.com/foo.html::201)");
+ // No line number or delimiting ':'.
+ AssertStackFrameInvalid("function_name (https://www.url.com/foo.html201)");
+ // No leading '(' around url, line, column.
+ AssertStackFrameInvalid(
+ "function_name https://www.url.com/foo.html:100:201)");
+ // No trailing ')'.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201");
+ // Trailing ' '.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201) ");
+ // Invalid column number.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201a)");
+ // Negative column number.
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:-201)");
+ // Extra trailing ')'
+ AssertStackFrameInvalid(
+ "function_name (https://www.url.com/foo.html:100:201))");
+}
+
+} // namespace extensions