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/stringprintf.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "chrome/browser/extensions/extension_browsertest.h" 13 #include "chrome/browser/extensions/extension_toolbar_model.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/common/pref_names.h" 16 #include "chrome/test/base/ui_test_utils.h" 17 #include "extensions/browser/extension_error.h" 18 #include "extensions/common/constants.h" 19 #include "extensions/common/error_utils.h" 20 #include "extensions/common/extension.h" 21 #include "extensions/common/extension_urls.h" 22 #include "extensions/common/feature_switch.h" 23 #include "extensions/common/manifest_constants.h" 24 #include "net/test/embedded_test_server/embedded_test_server.h" 25 #include "testing/gtest/include/gtest/gtest.h" 26 #include "url/gurl.h" 27 28 using base::string16; 29 using base::UTF8ToUTF16; 30 31 namespace extensions { 32 33 namespace { 34 35 const char kTestingPage[] = "/extensions/test_file.html"; 36 const char kAnonymousFunction[] = "(anonymous function)"; 37 const char* kBackgroundPageName = 38 extensions::kGeneratedBackgroundPageFilename; 39 const int kNoFlags = 0; 40 41 const StackTrace& GetStackTraceFromError(const ExtensionError* error) { 42 CHECK(error->type() == ExtensionError::RUNTIME_ERROR); 43 return (static_cast<const RuntimeError*>(error))->stack_trace(); 44 } 45 46 // Verify that a given |frame| has the proper source and function name. 47 void CheckStackFrame(const StackFrame& frame, 48 const std::string& source, 49 const std::string& function) { 50 EXPECT_EQ(UTF8ToUTF16(source), frame.source); 51 EXPECT_EQ(UTF8ToUTF16(function), frame.function); 52 } 53 54 // Verify that all properties of a given |frame| are correct. Overloaded because 55 // we commonly do not check line/column numbers, as they are too likely 56 // to change. 57 void CheckStackFrame(const StackFrame& frame, 58 const std::string& source, 59 const std::string& function, 60 size_t line_number, 61 size_t column_number) { 62 CheckStackFrame(frame, source, function); 63 EXPECT_EQ(line_number, frame.line_number); 64 EXPECT_EQ(column_number, frame.column_number); 65 } 66 67 // Verify that all properties of a given |error| are correct. 68 void CheckError(const ExtensionError* error, 69 ExtensionError::Type type, 70 const std::string& id, 71 const std::string& source, 72 bool from_incognito, 73 const std::string& message) { 74 ASSERT_TRUE(error); 75 EXPECT_EQ(type, error->type()); 76 EXPECT_EQ(id, error->extension_id()); 77 EXPECT_EQ(UTF8ToUTF16(source), error->source()); 78 EXPECT_EQ(from_incognito, error->from_incognito()); 79 EXPECT_EQ(UTF8ToUTF16(message), error->message()); 80 } 81 82 // Verify that all properties of a JS runtime error are correct. 83 void CheckRuntimeError(const ExtensionError* error, 84 const std::string& id, 85 const std::string& source, 86 bool from_incognito, 87 const std::string& message, 88 logging::LogSeverity level, 89 const GURL& context, 90 size_t expected_stack_size) { 91 CheckError(error, 92 ExtensionError::RUNTIME_ERROR, 93 id, 94 source, 95 from_incognito, 96 message); 97 98 const RuntimeError* runtime_error = static_cast<const RuntimeError*>(error); 99 EXPECT_EQ(level, runtime_error->level()); 100 EXPECT_EQ(context, runtime_error->context_url()); 101 EXPECT_EQ(expected_stack_size, runtime_error->stack_trace().size()); 102 } 103 104 void CheckManifestError(const ExtensionError* error, 105 const std::string& id, 106 const std::string& message, 107 const std::string& manifest_key, 108 const std::string& manifest_specific) { 109 CheckError(error, 110 ExtensionError::MANIFEST_ERROR, 111 id, 112 // source is always the manifest for ManifestErrors. 113 base::FilePath(kManifestFilename).AsUTF8Unsafe(), 114 false, // manifest errors are never from incognito. 115 message); 116 117 const ManifestError* manifest_error = 118 static_cast<const ManifestError*>(error); 119 EXPECT_EQ(UTF8ToUTF16(manifest_key), manifest_error->manifest_key()); 120 EXPECT_EQ(UTF8ToUTF16(manifest_specific), 121 manifest_error->manifest_specific()); 122 } 123 124 } // namespace 125 126 class ErrorConsoleBrowserTest : public ExtensionBrowserTest { 127 public: 128 ErrorConsoleBrowserTest() : error_console_(NULL) { } 129 virtual ~ErrorConsoleBrowserTest() { } 130 131 protected: 132 // A helper class in order to wait for the proper number of errors to be 133 // caught by the ErrorConsole. This will run the MessageLoop until a given 134 // number of errors are observed. 135 // Usage: 136 // ... 137 // ErrorObserver observer(3, error_console); 138 // <Cause three errors...> 139 // observer.WaitForErrors(); 140 // <Perform any additional checks...> 141 class ErrorObserver : public ErrorConsole::Observer { 142 public: 143 ErrorObserver(size_t errors_expected, ErrorConsole* error_console) 144 : errors_observed_(0), 145 errors_expected_(errors_expected), 146 waiting_(false), 147 error_console_(error_console) { 148 error_console_->AddObserver(this); 149 } 150 virtual ~ErrorObserver() { 151 if (error_console_) 152 error_console_->RemoveObserver(this); 153 } 154 155 // ErrorConsole::Observer implementation. 156 virtual void OnErrorAdded(const ExtensionError* error) OVERRIDE { 157 ++errors_observed_; 158 if (errors_observed_ >= errors_expected_) { 159 if (waiting_) 160 base::MessageLoopForUI::current()->Quit(); 161 } 162 } 163 164 virtual void OnErrorConsoleDestroyed() OVERRIDE { 165 error_console_ = NULL; 166 } 167 168 // Spin until the appropriate number of errors have been observed. 169 void WaitForErrors() { 170 if (errors_observed_ < errors_expected_) { 171 waiting_ = true; 172 content::RunMessageLoop(); 173 waiting_ = false; 174 } 175 } 176 177 private: 178 size_t errors_observed_; 179 size_t errors_expected_; 180 bool waiting_; 181 182 ErrorConsole* error_console_; 183 184 DISALLOW_COPY_AND_ASSIGN(ErrorObserver); 185 }; 186 187 // The type of action which we take after we load an extension in order to 188 // cause any errors. 189 enum Action { 190 ACTION_NAVIGATE, // navigate to a page to allow a content script to run. 191 ACTION_BROWSER_ACTION, // simulate a browser action click. 192 ACTION_NONE // Do nothing (errors will be caused by a background script, 193 // or by a manifest/loading warning). 194 }; 195 196 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 197 ExtensionBrowserTest::SetUpInProcessBrowserTestFixture(); 198 199 // We need to enable the ErrorConsole FeatureSwitch in order to collect 200 // errors. 201 FeatureSwitch::error_console()->SetOverrideValue( 202 FeatureSwitch::OVERRIDE_ENABLED); 203 } 204 205 virtual void SetUpOnMainThread() OVERRIDE { 206 ExtensionBrowserTest::SetUpOnMainThread(); 207 208 // Errors are only kept if we have Developer Mode enabled. 209 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true); 210 211 error_console_ = ErrorConsole::Get(profile()); 212 CHECK(error_console_); 213 214 test_data_dir_ = test_data_dir_.AppendASCII("error_console"); 215 } 216 217 const GURL& GetTestURL() { 218 if (test_url_.is_empty()) { 219 CHECK(embedded_test_server()->InitializeAndWaitUntilReady()); 220 test_url_ = embedded_test_server()->GetURL(kTestingPage); 221 } 222 return test_url_; 223 } 224 225 // Load the extension at |path|, take the specified |action|, and wait for 226 // |expected_errors| errors. Populate |extension| with a pointer to the loaded 227 // extension. 228 void LoadExtensionAndCheckErrors( 229 const std::string& path, 230 int flags, 231 size_t errors_expected, 232 Action action, 233 const Extension** extension) { 234 ErrorObserver observer(errors_expected, error_console_); 235 *extension = 236 LoadExtensionWithFlags(test_data_dir_.AppendASCII(path), flags); 237 ASSERT_TRUE(*extension); 238 239 switch (action) { 240 case ACTION_NAVIGATE: { 241 ui_test_utils::NavigateToURL(browser(), GetTestURL()); 242 break; 243 } 244 case ACTION_BROWSER_ACTION: { 245 ExtensionToolbarModel::Get(profile())->ExecuteBrowserAction( 246 *extension, browser(), NULL, true); 247 break; 248 } 249 case ACTION_NONE: 250 break; 251 default: 252 NOTREACHED(); 253 } 254 255 observer.WaitForErrors(); 256 257 // We should only have errors for a single extension, or should have no 258 // entries, if no errors were expected. 259 ASSERT_EQ(errors_expected > 0 ? 1u : 0u, error_console()->errors().size()); 260 ASSERT_EQ( 261 errors_expected, 262 error_console()->GetErrorsForExtension((*extension)->id()).size()); 263 } 264 265 ErrorConsole* error_console() { return error_console_; } 266 private: 267 // The URL used in testing for simple page navigations. 268 GURL test_url_; 269 270 // Weak reference to the ErrorConsole. 271 ErrorConsole* error_console_; 272 }; 273 274 // Test to ensure that we are successfully reporting manifest errors as an 275 // extension is installed. 276 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, ReportManifestErrors) { 277 const Extension* extension = NULL; 278 // We expect two errors - one for an invalid permission, and a second for 279 // an unknown key. 280 LoadExtensionAndCheckErrors("manifest_warnings", 281 ExtensionBrowserTest::kFlagIgnoreManifestWarnings, 282 2, 283 ACTION_NONE, 284 &extension); 285 286 const ErrorConsole::ErrorList& errors = 287 error_console()->GetErrorsForExtension(extension->id()); 288 289 // Unfortunately, there's not always a hard guarantee of order in parsing the 290 // manifest, so there's not a definitive order in which these errors may 291 // occur. As such, we need to determine which error corresponds to which 292 // expected error. 293 const ExtensionError* permissions_error = NULL; 294 const ExtensionError* unknown_key_error = NULL; 295 const char kFakeKey[] = "not_a_real_key"; 296 for (size_t i = 0; i < errors.size(); ++i) { 297 ASSERT_EQ(ExtensionError::MANIFEST_ERROR, errors[i]->type()); 298 std::string utf8_key = UTF16ToUTF8( 299 (static_cast<const ManifestError*>(errors[i]))->manifest_key()); 300 if (utf8_key == manifest_keys::kPermissions) 301 permissions_error = errors[i]; 302 else if (utf8_key == kFakeKey) 303 unknown_key_error = errors[i]; 304 } 305 ASSERT_TRUE(permissions_error); 306 ASSERT_TRUE(unknown_key_error); 307 308 const char kFakePermission[] = "not_a_real_permission"; 309 CheckManifestError(permissions_error, 310 extension->id(), 311 ErrorUtils::FormatErrorMessage( 312 manifest_errors::kPermissionUnknownOrMalformed, 313 kFakePermission), 314 manifest_keys::kPermissions, 315 kFakePermission); 316 317 CheckManifestError(unknown_key_error, 318 extension->id(), 319 ErrorUtils::FormatErrorMessage( 320 manifest_errors::kUnrecognizedManifestKey, 321 kFakeKey), 322 kFakeKey, 323 std::string()); 324 } 325 326 // Test that we do not store any errors unless the Developer Mode switch is 327 // toggled on the profile. 328 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, 329 DontStoreErrorsWithoutDeveloperMode) { 330 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, false); 331 332 const Extension* extension = NULL; 333 // Same test as ReportManifestErrors, except we don't expect any errors since 334 // we disable Developer Mode. 335 LoadExtensionAndCheckErrors("manifest_warnings", 336 ExtensionBrowserTest::kFlagIgnoreManifestWarnings, 337 0, 338 ACTION_NONE, 339 &extension); 340 341 // Now if we enable developer mode, the errors should be reported... 342 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true); 343 EXPECT_EQ(2u, error_console()->GetErrorsForExtension(extension->id()).size()); 344 345 // ... and if we disable it again, all errors which we were holding should be 346 // removed. 347 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, false); 348 EXPECT_EQ(0u, error_console()->GetErrorsForExtension(extension->id()).size()); 349 } 350 351 // Load an extension which, upon visiting any page, first sends out a console 352 // log, and then crashes with a JS TypeError. 353 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, 354 ContentScriptLogAndRuntimeError) { 355 const Extension* extension = NULL; 356 LoadExtensionAndCheckErrors( 357 "content_script_log_and_runtime_error", 358 kNoFlags, 359 2u, // Two errors: A log message and a JS type error. 360 ACTION_NAVIGATE, 361 &extension); 362 363 std::string script_url = extension->url().Resolve("content_script.js").spec(); 364 365 const ErrorConsole::ErrorList& errors = 366 error_console()->GetErrorsForExtension(extension->id()); 367 368 // The first error should be a console log. 369 CheckRuntimeError(errors[0], 370 extension->id(), 371 script_url, // The source should be the content script url. 372 false, // Not from incognito. 373 "Hello, World!", // The error message is the log. 374 logging::LOG_INFO, 375 GetTestURL(), // Content scripts run in the web page. 376 2u); 377 378 const StackTrace& stack_trace1 = GetStackTraceFromError(errors[0]); 379 CheckStackFrame(stack_trace1[0], 380 script_url, 381 "logHelloWorld", // function name 382 6u, // line number 383 11u /* column number */ ); 384 385 CheckStackFrame(stack_trace1[1], 386 script_url, 387 kAnonymousFunction, 388 9u, 389 1u); 390 391 // The second error should be a runtime error. 392 CheckRuntimeError(errors[1], 393 extension->id(), 394 script_url, 395 false, // not from incognito 396 "Uncaught TypeError: " 397 "Cannot set property 'foo' of undefined", 398 logging::LOG_ERROR, // JS errors are always ERROR level. 399 GetTestURL(), 400 1u); 401 402 const StackTrace& stack_trace2 = GetStackTraceFromError(errors[1]); 403 CheckStackFrame(stack_trace2[0], 404 script_url, 405 kAnonymousFunction, 406 12u, 407 1u); 408 } 409 410 // Catch an error from a BrowserAction; this is more complex than a content 411 // script error, since browser actions are routed through our own code. 412 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BrowserActionRuntimeError) { 413 const Extension* extension = NULL; 414 LoadExtensionAndCheckErrors( 415 "browser_action_runtime_error", 416 kNoFlags, 417 1u, // One error: A reference error from within the browser action. 418 ACTION_BROWSER_ACTION, 419 &extension); 420 421 std::string script_url = extension->url().Resolve("browser_action.js").spec(); 422 423 const ErrorConsole::ErrorList& errors = 424 error_console()->GetErrorsForExtension(extension->id()); 425 426 std::string event_bindings_str = 427 base::StringPrintf("extensions::%s", kEventBindings); 428 429 CheckRuntimeError( 430 errors[0], 431 extension->id(), 432 script_url, 433 false, // not incognito 434 "Error in event handler for browserAction.onClicked: " 435 "ReferenceError: baz is not defined", 436 logging::LOG_ERROR, 437 extension->url().Resolve(kBackgroundPageName), 438 6u); 439 440 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]); 441 442 CheckStackFrame(stack_trace[0], script_url, kAnonymousFunction); 443 CheckStackFrame(stack_trace[1], 444 "extensions::SafeBuiltins", 445 std::string("Function.target.") + kAnonymousFunction); 446 CheckStackFrame( 447 stack_trace[2], event_bindings_str, "Event.dispatchToListener"); 448 CheckStackFrame(stack_trace[3], event_bindings_str, "Event.dispatch_"); 449 CheckStackFrame(stack_trace[4], event_bindings_str, "dispatchArgs"); 450 CheckStackFrame(stack_trace[5], event_bindings_str, "dispatchEvent"); 451 } 452 453 // Test that we can catch an error for calling an API with improper arguments. 454 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIArgumentsRuntimeError) { 455 const Extension* extension = NULL; 456 LoadExtensionAndCheckErrors( 457 "bad_api_arguments_runtime_error", 458 kNoFlags, 459 1, // One error: call an API with improper arguments. 460 ACTION_NONE, 461 &extension); 462 463 const ErrorConsole::ErrorList& errors = 464 error_console()->GetErrorsForExtension(extension->id()); 465 466 std::string schema_utils_str = 467 base::StringPrintf("extensions::%s", kSchemaUtils); 468 469 CheckRuntimeError( 470 errors[0], 471 extension->id(), 472 schema_utils_str, // API calls are checked in schemaUtils.js. 473 false, // not incognito 474 "Uncaught Error: Invocation of form " 475 "tabs.get(string, function) doesn't match definition " 476 "tabs.get(integer tabId, function callback)", 477 logging::LOG_ERROR, 478 extension->url().Resolve(kBackgroundPageName), 479 1u); 480 481 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]); 482 ASSERT_EQ(1u, stack_trace.size()); 483 CheckStackFrame(stack_trace[0], 484 schema_utils_str, 485 kAnonymousFunction); 486 } 487 488 // Test that we catch an error when we try to call an API method without 489 // permission. 490 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIPermissionsRuntimeError) { 491 const Extension* extension = NULL; 492 LoadExtensionAndCheckErrors( 493 "bad_api_permissions_runtime_error", 494 kNoFlags, 495 1, // One error: we try to call addUrl() on chrome.history without 496 // permission, which results in a TypeError. 497 ACTION_NONE, 498 &extension); 499 500 std::string script_url = extension->url().Resolve("background.js").spec(); 501 502 const ErrorConsole::ErrorList& errors = 503 error_console()->GetErrorsForExtension(extension->id()); 504 505 CheckRuntimeError( 506 errors[0], 507 extension->id(), 508 script_url, 509 false, // not incognito 510 "Uncaught TypeError: Cannot call method 'addUrl' of undefined", 511 logging::LOG_ERROR, 512 extension->url().Resolve(kBackgroundPageName), 513 1u); 514 515 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]); 516 ASSERT_EQ(1u, stack_trace.size()); 517 CheckStackFrame(stack_trace[0], 518 script_url, 519 kAnonymousFunction, 520 5u, 1u); 521 } 522 523 } // namespace extensions 524