Index: chrome/browser/extensions/error_console/error_console_browsertest.cc |
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 0000000000000000000000000000000000000000..1cdbf6296bdd052f05fcc84bb8a5a810facb28b2 |
--- /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 |