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