OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/extensions/error_console/error_console.h" |
| 6 |
| 7 #include "base/files/file_path.h" |
| 8 #include "base/prefs/pref_service.h" |
| 9 #include "base/strings/string16.h" |
| 10 #include "base/strings/utf_string_conversions.h" |
| 11 #include "chrome/browser/extensions/extension_browsertest.h" |
| 12 #include "chrome/browser/extensions/extension_service.h" |
| 13 #include "chrome/browser/extensions/extension_system.h" |
| 14 #include "chrome/browser/extensions/extension_toolbar_model.h" |
| 15 #include "chrome/browser/profiles/profile.h" |
| 16 #include "chrome/common/extensions/extension.h" |
| 17 #include "chrome/common/extensions/extension_manifest_constants.h" |
| 18 #include "chrome/common/pref_names.h" |
| 19 #include "chrome/test/base/ui_test_utils.h" |
| 20 #include "chrome/test/base/ui_test_utils.h" |
| 21 #include "extensions/browser/extension_error.h" |
| 22 #include "extensions/common/constants.h" |
| 23 #include "extensions/common/error_utils.h" |
| 24 #include "extensions/common/manifest_constants.h" |
| 25 #include "net/test/embedded_test_server/embedded_test_server.h" |
| 26 #include "testing/gtest/include/gtest/gtest.h" |
| 27 #include "url/gurl.h" |
| 28 |
| 29 using base::string16; |
| 30 using base::UTF8ToUTF16; |
| 31 |
| 32 namespace extensions { |
| 33 |
| 34 namespace { |
| 35 |
| 36 namespace errors = extension_manifest_errors; |
| 37 |
| 38 const char kTestingPage[] = "/extensions/test_file.html"; |
| 39 const char kAnonymousFunction[] = "(anonymous function)"; |
| 40 const char* kBackgroundPageName = |
| 41 extensions::kGeneratedBackgroundPageFilename; |
| 42 const int kNoFlags = 0; |
| 43 |
| 44 const JavascriptRuntimeError::StackTrace& GetStackTraceFromError( |
| 45 const ExtensionError* error) { |
| 46 CHECK(error->type() == ExtensionError::JAVASCRIPT_RUNTIME_ERROR); |
| 47 return (static_cast<const JavascriptRuntimeError*>(error))->stack_trace(); |
| 48 } |
| 49 |
| 50 // Verify that a given |frame| has the proper url/source and function name. |
| 51 void CheckStackFrame(const JavascriptRuntimeError::StackFrame* frame, |
| 52 const std::string& url, |
| 53 const std::string& function) { |
| 54 EXPECT_EQ(UTF8ToUTF16(url), frame->url); |
| 55 EXPECT_EQ(UTF8ToUTF16(function), frame->function); |
| 56 } |
| 57 |
| 58 // Verify that all properties of a given |frame| are correct. Overloaded because |
| 59 // we commonly do not check line/column numbers, as they are too likely |
| 60 // to change. |
| 61 void CheckStackFrame(const JavascriptRuntimeError::StackFrame* frame, |
| 62 const std::string& url, |
| 63 const std::string& function, |
| 64 size_t line_number, |
| 65 size_t column_number) { |
| 66 CheckStackFrame(frame, url, function); |
| 67 EXPECT_EQ(line_number, frame->line_number); |
| 68 EXPECT_EQ(column_number, frame->column_number); |
| 69 } |
| 70 |
| 71 // Verify that all properties of a given |error| are correct. |
| 72 void CheckError(const ExtensionError* error, |
| 73 ExtensionError::Type type, |
| 74 const std::string& id, |
| 75 const std::string& source, |
| 76 bool from_incognito, |
| 77 const std::string& message) { |
| 78 ASSERT_TRUE(error); |
| 79 EXPECT_EQ(type, error->type()); |
| 80 EXPECT_EQ(id, error->extension_id()); |
| 81 EXPECT_EQ(UTF8ToUTF16(source), error->source()); |
| 82 EXPECT_EQ(from_incognito, error->from_incognito()); |
| 83 EXPECT_EQ(UTF8ToUTF16(message), error->message()); |
| 84 } |
| 85 |
| 86 // Verify that all properties of a JS runtime error are correct. |
| 87 void CheckRuntimeError(const ExtensionError* error, |
| 88 const std::string& id, |
| 89 const std::string& source, |
| 90 bool from_incognito, |
| 91 const std::string& message, |
| 92 logging::LogSeverity level, |
| 93 const GURL& context, |
| 94 size_t expected_stack_size) { |
| 95 CheckError(error, |
| 96 ExtensionError::JAVASCRIPT_RUNTIME_ERROR, |
| 97 id, |
| 98 source, |
| 99 from_incognito, |
| 100 message); |
| 101 |
| 102 const JavascriptRuntimeError* runtime_error = |
| 103 static_cast<const JavascriptRuntimeError*>(error); |
| 104 EXPECT_EQ(level, runtime_error->level()); |
| 105 EXPECT_EQ(context, runtime_error->context_url()); |
| 106 EXPECT_EQ(expected_stack_size, runtime_error->stack_trace().size()); |
| 107 } |
| 108 |
| 109 } // namespace |
| 110 |
| 111 class ErrorConsoleBrowserTest : public ExtensionBrowserTest { |
| 112 public: |
| 113 ErrorConsoleBrowserTest() : error_console_(NULL) { } |
| 114 virtual ~ErrorConsoleBrowserTest() { } |
| 115 |
| 116 protected: |
| 117 // A helper class in order to wait for the proper number of errors to be |
| 118 // caught by the ErrorConsole. This will run the MessageLoop until a given |
| 119 // number of errors are observed. |
| 120 // Usage: |
| 121 // ... |
| 122 // ErrorObserver observer(3, error_console); |
| 123 // <Cause three errors...> |
| 124 // observer.WaitForErrors(); |
| 125 // <Perform any additional checks...> |
| 126 class ErrorObserver : public ErrorConsole::Observer { |
| 127 public: |
| 128 ErrorObserver(size_t errors_expected, ErrorConsole* error_console) |
| 129 : errors_observed_(0), |
| 130 errors_expected_(errors_expected), |
| 131 waiting_(false), |
| 132 error_console_(error_console) { |
| 133 error_console_->AddObserver(this); |
| 134 } |
| 135 virtual ~ErrorObserver() { |
| 136 if (error_console_) |
| 137 error_console_->RemoveObserver(this); |
| 138 } |
| 139 |
| 140 // ErrorConsole::Observer implementation. |
| 141 virtual void OnErrorAdded(const ExtensionError* error) OVERRIDE { |
| 142 ++errors_observed_; |
| 143 if (errors_observed_ >= errors_expected_) { |
| 144 if (waiting_) |
| 145 base::MessageLoopForUI::current()->Quit(); |
| 146 } |
| 147 } |
| 148 |
| 149 virtual void OnErrorConsoleDestroyed() OVERRIDE { |
| 150 error_console_ = NULL; |
| 151 } |
| 152 |
| 153 // Spin until the appropriate number of errors have been observed. |
| 154 void WaitForErrors() { |
| 155 if (errors_observed_ < errors_expected_) { |
| 156 waiting_ = true; |
| 157 content::RunMessageLoop(); |
| 158 waiting_ = false; |
| 159 } |
| 160 } |
| 161 |
| 162 private: |
| 163 size_t errors_observed_; |
| 164 size_t errors_expected_; |
| 165 bool waiting_; |
| 166 |
| 167 ErrorConsole* error_console_; |
| 168 |
| 169 DISALLOW_COPY_AND_ASSIGN(ErrorObserver); |
| 170 }; |
| 171 |
| 172 // The type of action which we take after we load an extension in order to |
| 173 // cause any errors. |
| 174 enum Action { |
| 175 ACTION_NAVIGATE, // navigate to a page to allow a content script to run. |
| 176 ACTION_BROWSER_ACTION, // simulate a browser action click. |
| 177 ACTION_NONE // Do nothing (errors will be caused by a background script, |
| 178 // or by a manifest/loading warning). |
| 179 }; |
| 180 |
| 181 virtual void SetUpOnMainThread() OVERRIDE { |
| 182 ExtensionBrowserTest::SetUpOnMainThread(); |
| 183 |
| 184 // Errors are only kept if we have Developer Mode enabled. |
| 185 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true); |
| 186 |
| 187 error_console_ = ErrorConsole::Get(profile()); |
| 188 CHECK(error_console_); |
| 189 |
| 190 test_data_dir_ = test_data_dir_.AppendASCII("error_console"); |
| 191 } |
| 192 |
| 193 const GURL& GetTestURL() { |
| 194 if (test_url_.is_empty()) { |
| 195 CHECK(embedded_test_server()->InitializeAndWaitUntilReady()); |
| 196 test_url_ = embedded_test_server()->GetURL(kTestingPage); |
| 197 } |
| 198 return test_url_; |
| 199 } |
| 200 |
| 201 // Load the extension at |path|, take the specified |action|, and wait for |
| 202 // |expected_errors| errors. Populate |extension| with a pointer to the loaded |
| 203 // extension. |
| 204 void LoadExtensionAndCheckErrors( |
| 205 const std::string& path, |
| 206 int flags, |
| 207 size_t errors_expected, |
| 208 Action action, |
| 209 const Extension** extension) { |
| 210 ErrorObserver observer(errors_expected, error_console_); |
| 211 *extension = |
| 212 LoadExtensionWithFlags(test_data_dir_.AppendASCII(path), flags); |
| 213 ASSERT_TRUE(*extension); |
| 214 |
| 215 switch (action) { |
| 216 case ACTION_NAVIGATE: { |
| 217 ui_test_utils::NavigateToURL(browser(), GetTestURL()); |
| 218 break; |
| 219 } |
| 220 case ACTION_BROWSER_ACTION: { |
| 221 ExtensionService* service = |
| 222 extensions::ExtensionSystem::Get(profile())->extension_service(); |
| 223 service->toolbar_model()->ExecuteBrowserAction( |
| 224 *extension, browser(), NULL); |
| 225 break; |
| 226 } |
| 227 case ACTION_NONE: |
| 228 break; |
| 229 default: |
| 230 NOTREACHED(); |
| 231 } |
| 232 |
| 233 observer.WaitForErrors(); |
| 234 |
| 235 // We should only have errors for a single extension, or should have no |
| 236 // entries, if no errors were expected. |
| 237 ASSERT_EQ(errors_expected > 0 ? 1u : 0u, error_console()->errors().size()); |
| 238 ASSERT_EQ( |
| 239 errors_expected, |
| 240 error_console()->GetErrorsForExtension((*extension)->id()).size()); |
| 241 } |
| 242 |
| 243 ErrorConsole* error_console() { return error_console_; } |
| 244 private: |
| 245 // The URL used in testing for simple page navigations. |
| 246 GURL test_url_; |
| 247 |
| 248 // Weak reference to the ErrorConsole. |
| 249 ErrorConsole* error_console_; |
| 250 }; |
| 251 |
| 252 // Load an extension which, upon visiting any page, first sends out a console |
| 253 // log, and then crashes with a JS TypeError. |
| 254 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, |
| 255 ContentScriptLogAndRuntimeError) { |
| 256 const Extension* extension = NULL; |
| 257 LoadExtensionAndCheckErrors( |
| 258 "content_script_log_and_runtime_error", |
| 259 kNoFlags, |
| 260 2u, // Two errors: A log message and a JS type error. |
| 261 ACTION_NAVIGATE, |
| 262 &extension); |
| 263 |
| 264 std::string script_url = extension->url().Resolve("content_script.js").spec(); |
| 265 |
| 266 const ErrorConsole::ErrorList& errors = |
| 267 error_console()->GetErrorsForExtension(extension->id()); |
| 268 |
| 269 // The first error should be a console log. |
| 270 CheckRuntimeError(errors[0], |
| 271 extension->id(), |
| 272 script_url, // The source should be the content script url. |
| 273 false, // Not from incognito. |
| 274 "Hello, World!", // The error message is the log. |
| 275 logging::LOG_INFO, |
| 276 GetTestURL(), // Content scripts run in the web page. |
| 277 2u); |
| 278 |
| 279 const JavascriptRuntimeError::StackTrace& stack_trace1 = |
| 280 GetStackTraceFromError(errors[0]); |
| 281 CheckStackFrame(stack_trace1[0], |
| 282 script_url, |
| 283 "logHelloWorld", // function name |
| 284 6u, // line number |
| 285 11u /* column number */ ); |
| 286 |
| 287 CheckStackFrame(stack_trace1[1], |
| 288 script_url, |
| 289 kAnonymousFunction, |
| 290 9u, |
| 291 1u); |
| 292 |
| 293 // The second error should be a runtime error. |
| 294 CheckRuntimeError(errors[1], |
| 295 extension->id(), |
| 296 script_url, |
| 297 false, // not from incognito |
| 298 "Uncaught TypeError: " |
| 299 "Cannot set property 'foo' of undefined", |
| 300 logging::LOG_ERROR, // JS errors are always ERROR level. |
| 301 GetTestURL(), |
| 302 1u); |
| 303 |
| 304 const JavascriptRuntimeError::StackTrace& stack_trace2 = |
| 305 GetStackTraceFromError(errors[1]); |
| 306 CheckStackFrame(stack_trace2[0], |
| 307 script_url, |
| 308 kAnonymousFunction, |
| 309 12u, |
| 310 1u); |
| 311 } |
| 312 |
| 313 // Catch an error from a BrowserAction; this is more complex than a content |
| 314 // script error, since browser actions are routed through our own code. |
| 315 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BrowserActionRuntimeError) { |
| 316 const Extension* extension = NULL; |
| 317 LoadExtensionAndCheckErrors( |
| 318 "browser_action_runtime_error", |
| 319 kNoFlags, |
| 320 1u, // One error: A reference error from within the browser action. |
| 321 ACTION_BROWSER_ACTION, |
| 322 &extension); |
| 323 |
| 324 std::string event_bindings_str = "event_bindings"; |
| 325 std::string script_url = extension->url().Resolve("browser_action.js").spec(); |
| 326 |
| 327 const ErrorConsole::ErrorList& errors = |
| 328 error_console()->GetErrorsForExtension(extension->id()); |
| 329 |
| 330 CheckRuntimeError( |
| 331 errors[0], |
| 332 extension->id(), |
| 333 script_url, |
| 334 false, // not incognito |
| 335 "Error in event handler for browserAction.onClicked: " |
| 336 "ReferenceError: baz is not defined", |
| 337 logging::LOG_ERROR, |
| 338 extension->url().Resolve(kBackgroundPageName), |
| 339 6u); |
| 340 |
| 341 const JavascriptRuntimeError::StackTrace& stack_trace = |
| 342 GetStackTraceFromError(errors[0]); |
| 343 |
| 344 CheckStackFrame(stack_trace[0], script_url, kAnonymousFunction); |
| 345 CheckStackFrame(stack_trace[1], |
| 346 "extensions::SafeBuiltins", |
| 347 std::string("Function.target.") + kAnonymousFunction); |
| 348 CheckStackFrame( |
| 349 stack_trace[2], event_bindings_str, "Event.dispatchToListener"); |
| 350 CheckStackFrame(stack_trace[3], event_bindings_str, "Event.dispatch_"); |
| 351 CheckStackFrame(stack_trace[4], event_bindings_str, "dispatchArgs"); |
| 352 CheckStackFrame(stack_trace[5], event_bindings_str, "dispatchEvent"); |
| 353 } |
| 354 |
| 355 } // namespace extensions |
OLD | NEW |