Home | History | Annotate | Download | only in error_console
      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