diff options
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 |