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/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
GetStackTraceFromError(const ExtensionError * error)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.
CheckStackFrame(const StackFrame & frame,const std::string & source,const std::string & function)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.
CheckStackFrame(const StackFrame & frame,const std::string & source,const std::string & function,size_t line_number,size_t column_number)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.
CheckError(const ExtensionError * error,ExtensionError::Type type,const std::string & id,const std::string & source,bool from_incognito,const std::string & message)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.
CheckRuntimeError(const ExtensionError * error,const std::string & id,const std::string & source,bool from_incognito,const std::string & message,logging::LogSeverity level,const GURL & context,size_t expected_stack_size)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
CheckManifestError(const ExtensionError * error,const std::string & id,const std::string & message,const std::string & manifest_key,const std::string & manifest_specific)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:
ErrorConsoleBrowserTest()129 ErrorConsoleBrowserTest() : error_console_(NULL) { }
~ErrorConsoleBrowserTest()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:
ErrorObserver(size_t errors_expected,ErrorConsole * error_console)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 }
~ErrorObserver()151 virtual ~ErrorObserver() {
152 if (error_console_)
153 error_console_->RemoveObserver(this);
154 }
155
156 // ErrorConsole::Observer implementation.
OnErrorAdded(const ExtensionError * error)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
OnErrorConsoleDestroyed()165 virtual void OnErrorConsoleDestroyed() OVERRIDE {
166 error_console_ = NULL;
167 }
168
169 // Spin until the appropriate number of errors have been observed.
WaitForErrors()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
SetUpInProcessBrowserTestFixture()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
SetUpOnMainThread()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
GetTestURL()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.
LoadExtensionAndCheckErrors(const std::string & path,int flags,size_t errors_expected,Action action,const Extension ** 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 ExtensionToolbarModel::Get(profile())->ExecuteBrowserAction(
253 *extension, browser(), NULL, 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
error_console()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.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,ReportManifestErrors)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.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,DontStoreErrorsWithoutDeveloperMode)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.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,ContentScriptLogAndRuntimeError)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.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BrowserActionRuntimeError)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: baz is not defined\n"
452 "Stack trace: ReferenceError: baz is not defined",
453 logging::LOG_ERROR,
454 extension->url().Resolve(kBackgroundPageName),
455 8u);
456
457 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
458
459 CheckStackFrame(stack_trace[0], script_url, kAnonymousFunction);
460 CheckStackFrame(stack_trace[1],
461 "extensions::SafeBuiltins",
462 std::string("Function.target.") + kAnonymousFunction);
463 CheckStackFrame(
464 stack_trace[2], event_bindings_str, "EventImpl.dispatchToListener");
465 CheckStackFrame(stack_trace[3],
466 "extensions::SafeBuiltins",
467 std::string("Function.target.") + kAnonymousFunction);
468 CheckStackFrame(stack_trace[4], "extensions::utils",
469 event_dispatch_to_listener_str);
470 CheckStackFrame(stack_trace[5], event_bindings_str, "EventImpl.dispatch_");
471 CheckStackFrame(stack_trace[6], event_bindings_str, "dispatchArgs");
472 CheckStackFrame(stack_trace[7], event_bindings_str, "dispatchEvent");
473 }
474
475 // Test that we can catch an error for calling an API with improper arguments.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BadAPIArgumentsRuntimeError)476 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIArgumentsRuntimeError) {
477 const Extension* extension = NULL;
478 LoadExtensionAndCheckErrors(
479 "bad_api_arguments_runtime_error",
480 kNoFlags,
481 1, // One error: call an API with improper arguments.
482 ACTION_NONE,
483 &extension);
484
485 const ErrorList& errors =
486 error_console()->GetErrorsForExtension(extension->id());
487
488 std::string schema_utils_str =
489 base::StringPrintf("extensions::%s", kSchemaUtils);
490
491 CheckRuntimeError(
492 errors[0],
493 extension->id(),
494 schema_utils_str, // API calls are checked in schemaUtils.js.
495 false, // not incognito
496 "Uncaught Error: Invocation of form "
497 "tabs.get(string, function) doesn't match definition "
498 "tabs.get(integer tabId, function callback)",
499 logging::LOG_ERROR,
500 extension->url().Resolve(kBackgroundPageName),
501 1u);
502
503 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
504 ASSERT_EQ(1u, stack_trace.size());
505 CheckStackFrame(stack_trace[0],
506 schema_utils_str,
507 kAnonymousFunction);
508 }
509
510 // Test that we catch an error when we try to call an API method without
511 // permission.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BadAPIPermissionsRuntimeError)512 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIPermissionsRuntimeError) {
513 const Extension* extension = NULL;
514 LoadExtensionAndCheckErrors(
515 "bad_api_permissions_runtime_error",
516 kNoFlags,
517 1, // One error: we try to call addUrl() on chrome.history without
518 // permission, which results in a TypeError.
519 ACTION_NONE,
520 &extension);
521
522 std::string script_url = extension->url().Resolve("background.js").spec();
523
524 const ErrorList& errors =
525 error_console()->GetErrorsForExtension(extension->id());
526
527 CheckRuntimeError(
528 errors[0],
529 extension->id(),
530 script_url,
531 false, // not incognito
532 "Uncaught TypeError: Cannot read property 'addUrl' of undefined",
533 logging::LOG_ERROR,
534 extension->url().Resolve(kBackgroundPageName),
535 1u);
536
537 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
538 ASSERT_EQ(1u, stack_trace.size());
539 CheckStackFrame(stack_trace[0],
540 script_url,
541 kAnonymousFunction,
542 5u, 1u);
543 }
544
545 // Test that if there is an error in an HTML page loaded by an extension (most
546 // common with apps), it is caught and reported by the ErrorConsole.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BadExtensionPage)547 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadExtensionPage) {
548 const Extension* extension = NULL;
549 LoadExtensionAndCheckErrors(
550 "bad_extension_page",
551 kNoFlags,
552 1, // One error: the page will load JS which has a reference error.
553 ACTION_NEW_TAB,
554 &extension);
555 }
556
557 // Test that extension errors that go to chrome.runtime.lastError are caught
558 // and reported by the ErrorConsole.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,CatchesLastError)559 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, CatchesLastError) {
560 const Extension* extension = NULL;
561 LoadExtensionAndCheckErrors(
562 "trigger_last_error",
563 kNoFlags,
564 1, // One error, which is sent through last error when trying to remove
565 // a non-existent permisison.
566 ACTION_NONE,
567 &extension);
568
569 const ErrorList& errors =
570 error_console()->GetErrorsForExtension(extension->id());
571 ASSERT_EQ(1u, errors.size());
572
573 std::string script_url = extension->url().Resolve("background.js").spec();
574
575 CheckRuntimeError(
576 errors[0],
577 extension->id(),
578 script_url,
579 false, // not incognito
580 "Unchecked runtime.lastError while running permissions.remove: "
581 "'foobar' is not a recognized permission.",
582 logging::LOG_ERROR,
583 extension->url().Resolve(kBackgroundPageName),
584 1u);
585
586 const StackTrace& stack_trace = GetStackTraceFromError(errors[0]);
587 ASSERT_EQ(1u, stack_trace.size());
588 CheckStackFrame(stack_trace[0],
589 script_url,
590 kAnonymousFunction,
591 12u, 20u);
592 }
593
594 } // namespace extensions
595