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.
5 #include "chrome/browser/extensions/error_console/error_console.h"
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"
30 using base::UTF8ToUTF16
;
32 namespace extensions
{
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;
42 const StackTrace
& GetStackTraceFromError(const ExtensionError
* error
) {
43 CHECK(error
->type() == ExtensionError::RUNTIME_ERROR
);
44 return (static_cast<const RuntimeError
*>(error
))->stack_trace();
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
);
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
58 void CheckStackFrame(const StackFrame
& frame
,
59 const std::string
& source
,
60 const std::string
& function
,
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
);
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
,
74 const std::string
& message
) {
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());
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
,
88 const std::string
& message
,
89 logging::LogSeverity level
,
91 size_t expected_stack_size
) {
93 ExtensionError::RUNTIME_ERROR
,
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());
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
) {
111 ExtensionError::MANIFEST_ERROR
,
113 // source is always the manifest for ManifestErrors.
114 base::FilePath(kManifestFilename
).AsUTF8Unsafe(),
115 false, // manifest errors are never from incognito.
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());
127 class ErrorConsoleBrowserTest
: public ExtensionBrowserTest
{
129 ErrorConsoleBrowserTest() : error_console_(NULL
) { }
130 virtual ~ErrorConsoleBrowserTest() { }
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.
138 // ErrorObserver observer(3, error_console);
139 // <Cause three errors...>
140 // observer.WaitForErrors();
141 // <Perform any additional checks...>
142 class ErrorObserver
: public ErrorConsole::Observer
{
144 ErrorObserver(size_t errors_expected
, ErrorConsole
* error_console
)
145 : errors_observed_(0),
146 errors_expected_(errors_expected
),
148 error_console_(error_console
) {
149 error_console_
->AddObserver(this);
151 virtual ~ErrorObserver() {
153 error_console_
->RemoveObserver(this);
156 // ErrorConsole::Observer implementation.
157 void OnErrorAdded(const ExtensionError
* error
) override
{
159 if (errors_observed_
>= errors_expected_
) {
161 base::MessageLoopForUI::current()->Quit();
165 void OnErrorConsoleDestroyed() override
{ error_console_
= NULL
; }
167 // Spin until the appropriate number of errors have been observed.
168 void WaitForErrors() {
169 if (errors_observed_
< errors_expected_
) {
171 content::RunMessageLoop();
177 size_t errors_observed_
;
178 size_t errors_expected_
;
181 ErrorConsole
* error_console_
;
183 DISALLOW_COPY_AND_ASSIGN(ErrorObserver
);
186 // The type of action which we take after we load an extension in order to
189 // Navigate to a (non-chrome) page to allow a content script to run.
191 // Simulate a browser action click.
192 ACTION_BROWSER_ACTION
,
193 // Navigate to the new tab page.
195 // Do nothing (errors will be caused by a background script,
196 // or by a manifest/loading warning).
200 void SetUpInProcessBrowserTestFixture() override
{
201 ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
203 // We need to enable the ErrorConsole FeatureSwitch in order to collect
204 // errors. This should be enabled on any channel <= Dev, but let's make
205 // sure (in case a test is running on, e.g., a beta channel).
206 FeatureSwitch::error_console()->SetOverrideValue(
207 FeatureSwitch::OVERRIDE_ENABLED
);
210 void SetUpOnMainThread() override
{
211 ExtensionBrowserTest::SetUpOnMainThread();
213 // Errors are only kept if we have Developer Mode enabled.
214 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode
, true);
216 error_console_
= ErrorConsole::Get(profile());
217 CHECK(error_console_
);
219 test_data_dir_
= test_data_dir_
.AppendASCII("error_console");
222 const GURL
& GetTestURL() {
223 if (test_url_
.is_empty()) {
224 CHECK(embedded_test_server()->InitializeAndWaitUntilReady());
225 test_url_
= embedded_test_server()->GetURL(kTestingPage
);
230 // Load the extension at |path|, take the specified |action|, and wait for
231 // |expected_errors| errors. Populate |extension| with a pointer to the loaded
233 void LoadExtensionAndCheckErrors(
234 const std::string
& path
,
236 size_t errors_expected
,
238 const Extension
** extension
) {
239 ErrorObserver
observer(errors_expected
, error_console_
);
241 LoadExtensionWithFlags(test_data_dir_
.AppendASCII(path
), flags
);
242 ASSERT_TRUE(*extension
);
245 case ACTION_NAVIGATE
: {
246 ui_test_utils::NavigateToURL(browser(), GetTestURL());
249 case ACTION_BROWSER_ACTION
: {
250 ExtensionActionAPI::Get(profile())->ExecuteExtensionAction(
251 *extension
, browser(), true);
254 case ACTION_NEW_TAB
: {
255 ui_test_utils::NavigateToURL(browser(),
256 GURL(chrome::kChromeUINewTabURL
));
265 observer
.WaitForErrors();
267 // We should only have errors for a single extension, or should have no
268 // entries, if no errors were expected.
269 ASSERT_EQ(errors_expected
> 0 ? 1u : 0u,
270 error_console()->get_num_entries_for_test());
273 error_console()->GetErrorsForExtension((*extension
)->id()).size());
276 ErrorConsole
* error_console() { return error_console_
; }
278 // The URL used in testing for simple page navigations.
281 // Weak reference to the ErrorConsole.
282 ErrorConsole
* error_console_
;
285 // Test to ensure that we are successfully reporting manifest errors as an
286 // extension is installed.
287 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
, ReportManifestErrors
) {
288 const Extension
* extension
= NULL
;
289 // We expect two errors - one for an invalid permission, and a second for
291 LoadExtensionAndCheckErrors("manifest_warnings",
292 ExtensionBrowserTest::kFlagIgnoreManifestWarnings
,
297 const ErrorList
& errors
=
298 error_console()->GetErrorsForExtension(extension
->id());
300 // Unfortunately, there's not always a hard guarantee of order in parsing the
301 // manifest, so there's not a definitive order in which these errors may
302 // occur. As such, we need to determine which error corresponds to which
304 const ExtensionError
* permissions_error
= NULL
;
305 const ExtensionError
* unknown_key_error
= NULL
;
306 const char kFakeKey
[] = "not_a_real_key";
307 for (size_t i
= 0; i
< errors
.size(); ++i
) {
308 ASSERT_EQ(ExtensionError::MANIFEST_ERROR
, errors
[i
]->type());
309 std::string utf8_key
= base::UTF16ToUTF8(
310 (static_cast<const ManifestError
*>(errors
[i
]))->manifest_key());
311 if (utf8_key
== manifest_keys::kPermissions
)
312 permissions_error
= errors
[i
];
313 else if (utf8_key
== kFakeKey
)
314 unknown_key_error
= errors
[i
];
316 ASSERT_TRUE(permissions_error
);
317 ASSERT_TRUE(unknown_key_error
);
319 const char kFakePermission
[] = "not_a_real_permission";
320 CheckManifestError(permissions_error
,
322 ErrorUtils::FormatErrorMessage(
323 manifest_errors::kPermissionUnknownOrMalformed
,
325 manifest_keys::kPermissions
,
328 CheckManifestError(unknown_key_error
,
330 ErrorUtils::FormatErrorMessage(
331 manifest_errors::kUnrecognizedManifestKey
,
337 // Test that we do not store any errors unless the Developer Mode switch is
338 // toggled on the profile.
339 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
,
340 DontStoreErrorsWithoutDeveloperMode
) {
341 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode
, false);
343 const Extension
* extension
= NULL
;
344 // Same test as ReportManifestErrors, except we don't expect any errors since
345 // we disable Developer Mode.
346 LoadExtensionAndCheckErrors("manifest_warnings",
347 ExtensionBrowserTest::kFlagIgnoreManifestWarnings
,
352 // Now if we enable developer mode, the errors should be reported...
353 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode
, true);
354 EXPECT_EQ(2u, error_console()->GetErrorsForExtension(extension
->id()).size());
356 // ... and if we disable it again, all errors which we were holding should be
358 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode
, false);
359 EXPECT_EQ(0u, error_console()->GetErrorsForExtension(extension
->id()).size());
362 // Load an extension which, upon visiting any page, first sends out a console
363 // log, and then crashes with a JS TypeError.
364 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
,
365 ContentScriptLogAndRuntimeError
) {
366 const Extension
* extension
= NULL
;
367 LoadExtensionAndCheckErrors(
368 "content_script_log_and_runtime_error",
370 2u, // Two errors: A log message and a JS type error.
374 std::string script_url
= extension
->url().Resolve("content_script.js").spec();
376 const ErrorList
& errors
=
377 error_console()->GetErrorsForExtension(extension
->id());
379 // The first error should be a console log.
380 CheckRuntimeError(errors
[0],
382 script_url
, // The source should be the content script url.
383 false, // Not from incognito.
384 "Hello, World!", // The error message is the log.
386 GetTestURL(), // Content scripts run in the web page.
389 const StackTrace
& stack_trace1
= GetStackTraceFromError(errors
[0]);
390 CheckStackFrame(stack_trace1
[0],
392 "logHelloWorld", // function name
394 11u /* column number */ );
396 CheckStackFrame(stack_trace1
[1],
402 // The second error should be a runtime error.
403 CheckRuntimeError(errors
[1],
406 false, // not from incognito
407 "Uncaught TypeError: "
408 "Cannot set property 'foo' of undefined",
409 logging::LOG_ERROR
, // JS errors are always ERROR level.
413 const StackTrace
& stack_trace2
= GetStackTraceFromError(errors
[1]);
414 CheckStackFrame(stack_trace2
[0],
421 // Catch an error from a BrowserAction; this is more complex than a content
422 // script error, since browser actions are routed through our own code.
423 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
, BrowserActionRuntimeError
) {
424 const Extension
* extension
= NULL
;
425 LoadExtensionAndCheckErrors(
426 "browser_action_runtime_error",
428 1u, // One error: A reference error from within the browser action.
429 ACTION_BROWSER_ACTION
,
432 std::string script_url
= extension
->url().Resolve("browser_action.js").spec();
434 const ErrorList
& errors
=
435 error_console()->GetErrorsForExtension(extension
->id());
437 std::string event_bindings_str
=
438 base::StringPrintf("extensions::%s", kEventBindings
);
440 std::string event_dispatch_to_listener_str
=
441 base::StringPrintf("Event.publicClass.%s [as dispatchToListener]",
448 false, // not incognito
449 "Error in event handler for browserAction.onClicked: ReferenceError: "
450 "baz is not defined",
452 extension
->url().Resolve(kBackgroundPageName
),
455 const StackTrace
& stack_trace
= GetStackTraceFromError(errors
[0]);
456 // Note: This test used to have a stack trace of length 6 that contains stack
457 // frames in the extension code, but since crbug.com/404406 was fixed only
458 // stack frames within user-defined extension code are printed.
460 CheckStackFrame(stack_trace
[0], script_url
, kAnonymousFunction
);
463 // Test that we can catch an error for calling an API with improper arguments.
464 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
, BadAPIArgumentsRuntimeError
) {
465 const Extension
* extension
= NULL
;
466 LoadExtensionAndCheckErrors(
467 "bad_api_arguments_runtime_error",
469 1, // One error: call an API with improper arguments.
473 const ErrorList
& errors
=
474 error_console()->GetErrorsForExtension(extension
->id());
476 std::string schema_utils_str
=
477 base::StringPrintf("extensions::%s", kSchemaUtils
);
482 schema_utils_str
, // API calls are checked in schemaUtils.js.
483 false, // not incognito
484 "Uncaught Error: Invocation of form "
485 "tabs.get(string, function) doesn't match definition "
486 "tabs.get(integer tabId, function callback)",
488 extension
->url().Resolve(kBackgroundPageName
),
491 const StackTrace
& stack_trace
= GetStackTraceFromError(errors
[0]);
492 ASSERT_EQ(1u, stack_trace
.size());
493 CheckStackFrame(stack_trace
[0],
498 // Test that we catch an error when we try to call an API method without
500 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
, BadAPIPermissionsRuntimeError
) {
501 const Extension
* extension
= NULL
;
502 LoadExtensionAndCheckErrors(
503 "bad_api_permissions_runtime_error",
505 1, // One error: we try to call addUrl() on chrome.history without
506 // permission, which results in a TypeError.
510 std::string script_url
= extension
->url().Resolve("background.js").spec();
512 const ErrorList
& errors
=
513 error_console()->GetErrorsForExtension(extension
->id());
519 false, // not incognito
520 "Uncaught TypeError: Cannot read property 'addUrl' of undefined",
522 extension
->url().Resolve(kBackgroundPageName
),
525 const StackTrace
& stack_trace
= GetStackTraceFromError(errors
[0]);
526 ASSERT_EQ(1u, stack_trace
.size());
527 CheckStackFrame(stack_trace
[0],
533 // Test that if there is an error in an HTML page loaded by an extension (most
534 // common with apps), it is caught and reported by the ErrorConsole.
535 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
, BadExtensionPage
) {
536 const Extension
* extension
= NULL
;
537 LoadExtensionAndCheckErrors(
538 "bad_extension_page",
540 1, // One error: the page will load JS which has a reference error.
545 // Test that extension errors that go to chrome.runtime.lastError are caught
546 // and reported by the ErrorConsole.
547 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest
, CatchesLastError
) {
548 const Extension
* extension
= NULL
;
549 LoadExtensionAndCheckErrors(
550 "trigger_last_error",
552 1, // One error, which is sent through last error when trying to remove
553 // a non-existent permisison.
557 const ErrorList
& errors
=
558 error_console()->GetErrorsForExtension(extension
->id());
559 ASSERT_EQ(1u, errors
.size());
561 std::string script_url
= extension
->url().Resolve("background.js").spec();
567 false, // not incognito
568 "Unchecked runtime.lastError while running permissions.remove: "
569 "'foobar' is not a recognized permission.",
571 extension
->url().Resolve(kBackgroundPageName
),
574 const StackTrace
& stack_trace
= GetStackTraceFromError(errors
[0]);
575 ASSERT_EQ(1u, stack_trace
.size());
576 CheckStackFrame(stack_trace
[0],
582 } // namespace extensions