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/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