Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / error_console / error_console_browsertest.cc
bloba290b45335e7b0782da6b0fe9ed7bdbd5e899641
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"
27 #include "url/gurl.h"
29 using base::string16;
30 using base::UTF8ToUTF16;
32 namespace extensions {
34 namespace {
36 const char kTestingPage[] = "/extensions/test_file.html";
37 const char kAnonymousFunction[] = "(anonymous function)";
38 const char* const 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
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);
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());
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);
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) {
110 CheckError(error,
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.
116 message);
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());
125 } // namespace
127 class ErrorConsoleBrowserTest : public ExtensionBrowserTest {
128 public:
129 ErrorConsoleBrowserTest() : error_console_(NULL) { }
130 ~ErrorConsoleBrowserTest() override {}
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);
151 virtual ~ErrorObserver() {
152 if (error_console_)
153 error_console_->RemoveObserver(this);
156 // ErrorConsole::Observer implementation.
157 void OnErrorAdded(const ExtensionError* error) override {
158 ++errors_observed_;
159 if (errors_observed_ >= errors_expected_) {
160 if (waiting_)
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_) {
170 waiting_ = true;
171 content::RunMessageLoop();
172 waiting_ = false;
176 private:
177 size_t errors_observed_;
178 size_t errors_expected_;
179 bool waiting_;
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
187 // cause any errors.
188 enum Action {
189 // Navigate to a (non-chrome) page to allow a content script to run.
190 ACTION_NAVIGATE,
191 // Simulate a browser action click.
192 ACTION_BROWSER_ACTION,
193 // Navigate to the new tab page.
194 ACTION_NEW_TAB,
195 // Do nothing (errors will be caused by a background script,
196 // or by a manifest/loading warning).
197 ACTION_NONE
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);
227 return test_url_;
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
232 // extension.
233 void LoadExtensionAndCheckErrors(
234 const std::string& path,
235 int flags,
236 size_t errors_expected,
237 Action action,
238 const Extension** extension) {
239 ErrorObserver observer(errors_expected, error_console_);
240 *extension =
241 LoadExtensionWithFlags(test_data_dir_.AppendASCII(path), flags);
242 ASSERT_TRUE(*extension);
244 switch (action) {
245 case ACTION_NAVIGATE: {
246 ui_test_utils::NavigateToURL(browser(), GetTestURL());
247 break;
249 case ACTION_BROWSER_ACTION: {
250 ExtensionActionAPI::Get(profile())->ExecuteExtensionAction(
251 *extension, browser(), true);
252 break;
254 case ACTION_NEW_TAB: {
255 ui_test_utils::NavigateToURL(browser(),
256 GURL(chrome::kChromeUINewTabURL));
257 break;
259 case ACTION_NONE:
260 break;
261 default:
262 NOTREACHED();
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());
271 ASSERT_EQ(
272 errors_expected,
273 error_console()->GetErrorsForExtension((*extension)->id()).size());
276 ErrorConsole* error_console() { return error_console_; }
278 private:
279 // The URL used in testing for simple page navigations.
280 GURL test_url_;
282 // Weak reference to the ErrorConsole.
283 ErrorConsole* error_console_;
286 // Test to ensure that we are successfully reporting manifest errors as an
287 // extension is installed.
288 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, ReportManifestErrors) {
289 const Extension* extension = NULL;
290 // We expect two errors - one for an invalid permission, and a second for
291 // an unknown key.
292 LoadExtensionAndCheckErrors("manifest_warnings",
293 ExtensionBrowserTest::kFlagIgnoreManifestWarnings,
295 ACTION_NONE,
296 &extension);
298 const ErrorList& errors =
299 error_console()->GetErrorsForExtension(extension->id());
301 // Unfortunately, there's not always a hard guarantee of order in parsing the
302 // manifest, so there's not a definitive order in which these errors may
303 // occur. As such, we need to determine which error corresponds to which
304 // expected error.
305 const ExtensionError* permissions_error = NULL;
306 const ExtensionError* unknown_key_error = NULL;
307 const char kFakeKey[] = "not_a_real_key";
308 for (size_t i = 0; i < errors.size(); ++i) {
309 ASSERT_EQ(ExtensionError::MANIFEST_ERROR, errors[i]->type());
310 std::string utf8_key = base::UTF16ToUTF8(
311 (static_cast<const ManifestError*>(errors[i]))->manifest_key());
312 if (utf8_key == manifest_keys::kPermissions)
313 permissions_error = errors[i];
314 else if (utf8_key == kFakeKey)
315 unknown_key_error = errors[i];
317 ASSERT_TRUE(permissions_error);
318 ASSERT_TRUE(unknown_key_error);
320 const char kFakePermission[] = "not_a_real_permission";
321 CheckManifestError(permissions_error,
322 extension->id(),
323 ErrorUtils::FormatErrorMessage(
324 manifest_errors::kPermissionUnknownOrMalformed,
325 kFakePermission),
326 manifest_keys::kPermissions,
327 kFakePermission);
329 CheckManifestError(unknown_key_error,
330 extension->id(),
331 ErrorUtils::FormatErrorMessage(
332 manifest_errors::kUnrecognizedManifestKey,
333 kFakeKey),
334 kFakeKey,
335 std::string());
338 // Test that we do not store any errors unless the Developer Mode switch is
339 // toggled on the profile.
340 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,
341 DontStoreErrorsWithoutDeveloperMode) {
342 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, false);
344 const Extension* extension = NULL;
345 // Same test as ReportManifestErrors, except we don't expect any errors since
346 // we disable Developer Mode.
347 LoadExtensionAndCheckErrors("manifest_warnings",
348 ExtensionBrowserTest::kFlagIgnoreManifestWarnings,
350 ACTION_NONE,
351 &extension);
353 // Now if we enable developer mode, the errors should be reported...
354 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
355 EXPECT_EQ(2u, error_console()->GetErrorsForExtension(extension->id()).size());
357 // ... and if we disable it again, all errors which we were holding should be
358 // removed.
359 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, false);
360 EXPECT_EQ(0u, error_console()->GetErrorsForExtension(extension->id()).size());
363 // Load an extension which, upon visiting any page, first sends out a console
364 // log, and then crashes with a JS TypeError.
365 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,
366 ContentScriptLogAndRuntimeError) {
367 const Extension* extension = NULL;
368 LoadExtensionAndCheckErrors(
369 "content_script_log_and_runtime_error",
370 kNoFlags,
371 2u, // Two errors: A log message and a JS type error.
372 ACTION_NAVIGATE,
373 &extension);
375 std::string script_url = extension->url().Resolve("content_script.js").spec();
377 const ErrorList& errors =
378 error_console()->GetErrorsForExtension(extension->id());
380 // The first error should be a console log.
381 CheckRuntimeError(errors[0],
382 extension->id(),
383 script_url, // The source should be the content script url.
384 false, // Not from incognito.
385 "Hello, World!", // The error message is the log.
386 logging::LOG_INFO,
387 GetTestURL(), // Content scripts run in the web page.
388 2u);
390 const StackTrace& stack_trace1 = GetStackTraceFromError(errors[0]);
391 CheckStackFrame(stack_trace1[0],
392 script_url,
393 "logHelloWorld", // function name
394 6u, // line number
395 11u /* column number */);
397 CheckStackFrame(stack_trace1[1],
398 script_url,
399 kAnonymousFunction,
401 1u);
403 // The second error should be a runtime error.
404 CheckRuntimeError(errors[1],
405 extension->id(),
406 script_url,
407 false, // not from incognito
408 "Uncaught TypeError: "
409 "Cannot set property 'foo' of undefined",
410 logging::LOG_ERROR, // JS errors are always ERROR level.
411 GetTestURL(),
412 1u);
414 const StackTrace& stack_trace2 = GetStackTraceFromError(errors[1]);
415 CheckStackFrame(stack_trace2[0],
416 script_url,
417 kAnonymousFunction,
418 12u,
419 1u);
422 // Catch an error from a BrowserAction; this is more complex than a content
423 // script error, since browser actions are routed through our own code.
424 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BrowserActionRuntimeError) {
425 const Extension* extension = NULL;
426 LoadExtensionAndCheckErrors(
427 "browser_action_runtime_error",
428 kNoFlags,
429 1u, // One error: A reference error from within the browser action.
430 ACTION_BROWSER_ACTION,
431 &extension);
433 std::string script_url = extension->url().Resolve("browser_action.js").spec();
435 const ErrorList& errors =
436 error_console()->GetErrorsForExtension(extension->id());
438 std::string event_bindings_str =
439 base::StringPrintf("extensions::%s", kEventBindings);
441 std::string event_dispatch_to_listener_str =
442 base::StringPrintf("Event.publicClass.%s [as dispatchToListener]",
443 kAnonymousFunction);
445 CheckRuntimeError(
446 errors[0],
447 extension->id(),
448 script_url,
449 false, // not incognito
450 "Error in event handler for browserAction.onClicked: ReferenceError: "
451 "baz is not defined",
452 logging::LOG_ERROR,
453 extension->url().Resolve(kBackgroundPageName),
454 1u);
456 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
457 // Note: This test used to have a stack trace of length 6 that contains stack
458 // frames in the extension code, but since crbug.com/404406 was fixed only
459 // stack frames within user-defined extension code are printed.
461 CheckStackFrame(stack_trace[0], script_url, kAnonymousFunction);
464 // Test that we can catch an error for calling an API with improper arguments.
465 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIArgumentsRuntimeError) {
466 const Extension* extension = NULL;
467 LoadExtensionAndCheckErrors(
468 "bad_api_arguments_runtime_error",
469 kNoFlags,
470 1, // One error: call an API with improper arguments.
471 ACTION_NONE,
472 &extension);
474 const ErrorList& errors =
475 error_console()->GetErrorsForExtension(extension->id());
477 std::string schema_utils_str =
478 base::StringPrintf("extensions::%s", kSchemaUtils);
480 CheckRuntimeError(
481 errors[0],
482 extension->id(),
483 schema_utils_str, // API calls are checked in schemaUtils.js.
484 false, // not incognito
485 "Uncaught Error: Invocation of form "
486 "tabs.get(string, function) doesn't match definition "
487 "tabs.get(integer tabId, function callback)",
488 logging::LOG_ERROR,
489 extension->url().Resolve(kBackgroundPageName),
490 1u);
492 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
493 ASSERT_EQ(1u, stack_trace.size());
494 CheckStackFrame(stack_trace[0],
495 schema_utils_str,
496 kAnonymousFunction);
499 // Test that we catch an error when we try to call an API method without
500 // permission.
501 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIPermissionsRuntimeError) {
502 const Extension* extension = NULL;
503 LoadExtensionAndCheckErrors(
504 "bad_api_permissions_runtime_error",
505 kNoFlags,
506 1, // One error: we try to call addUrl() on chrome.history without
507 // permission, which results in a TypeError.
508 ACTION_NONE,
509 &extension);
511 std::string script_url = extension->url().Resolve("background.js").spec();
513 const ErrorList& errors =
514 error_console()->GetErrorsForExtension(extension->id());
516 CheckRuntimeError(
517 errors[0],
518 extension->id(),
519 script_url,
520 false, // not incognito
521 "Uncaught TypeError: Cannot read property 'addUrl' of undefined",
522 logging::LOG_ERROR,
523 extension->url().Resolve(kBackgroundPageName),
524 1u);
526 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
527 ASSERT_EQ(1u, stack_trace.size());
528 CheckStackFrame(stack_trace[0],
529 script_url,
530 kAnonymousFunction,
531 5u, 1u);
534 // Test that if there is an error in an HTML page loaded by an extension (most
535 // common with apps), it is caught and reported by the ErrorConsole.
536 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadExtensionPage) {
537 const Extension* extension = NULL;
538 LoadExtensionAndCheckErrors(
539 "bad_extension_page",
540 kNoFlags,
541 1, // One error: the page will load JS which has a reference error.
542 ACTION_NEW_TAB,
543 &extension);
546 // Test that extension errors that go to chrome.runtime.lastError are caught
547 // and reported by the ErrorConsole.
548 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, CatchesLastError) {
549 const Extension* extension = NULL;
550 LoadExtensionAndCheckErrors(
551 "trigger_last_error",
552 kNoFlags,
553 1, // One error, which is sent through last error when trying to remove
554 // a non-existent permisison.
555 ACTION_NONE,
556 &extension);
558 const ErrorList& errors =
559 error_console()->GetErrorsForExtension(extension->id());
560 ASSERT_EQ(1u, errors.size());
562 std::string script_url = extension->url().Resolve("background.js").spec();
564 CheckRuntimeError(
565 errors[0],
566 extension->id(),
567 script_url,
568 false, // not incognito
569 "Unchecked runtime.lastError while running permissions.remove: "
570 "'foobar' is not a recognized permission.",
571 logging::LOG_ERROR,
572 extension->url().Resolve(kBackgroundPageName),
573 1u);
575 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
576 ASSERT_EQ(1u, stack_trace.size());
577 CheckStackFrame(stack_trace[0],
578 script_url,
579 kAnonymousFunction,
580 12u, 20u);
583 } // namespace extensions