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
28using base::string16;
29using base::UTF8ToUTF16;
30
31namespace extensions {
32
33namespace {
34
35const char kTestingPage[] = "/extensions/test_file.html";
36const char kAnonymousFunction[] = "(anonymous function)";
37const char* kBackgroundPageName =
38    extensions::kGeneratedBackgroundPageFilename;
39const int kNoFlags = 0;
40
41const 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.
47void 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.
57void 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.
68void 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.
83void 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
104void 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
126class 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.
276IN_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.
328IN_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.
353IN_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.
412IN_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.
454IN_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.
490IN_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