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 <stddef.h>
8 
9 #include "base/files/file_path.h"
10 #include "base/macros.h"
11 #include "base/run_loop.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "build/build_config.h"
16 #include "chrome/browser/extensions/extension_action_runner.h"
17 #include "chrome/browser/extensions/extension_browsertest.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/common/pref_names.h"
21 #include "chrome/common/url_constants.h"
22 #include "chrome/test/base/ui_test_utils.h"
23 #include "components/prefs/pref_service.h"
24 #include "content/public/test/browser_test.h"
25 #include "extensions/browser/extension_error.h"
26 #include "extensions/common/constants.h"
27 #include "extensions/common/error_utils.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_urls.h"
30 #include "extensions/common/manifest_constants.h"
31 #include "net/test/embedded_test_server/embedded_test_server.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "url/gurl.h"
34 
35 using base::string16;
36 using base::UTF8ToUTF16;
37 
38 namespace extensions {
39 
40 namespace {
41 
42 const char kTestingPage[] = "/extensions/test_file.html";
43 const char kAnonymousFunction[] = "(anonymous function)";
44 const char* const kBackgroundPageName =
45     extensions::kGeneratedBackgroundPageFilename;
46 const int kNoFlags = 0;
47 
GetStackTraceFromError(const ExtensionError * error)48 const StackTrace& GetStackTraceFromError(const ExtensionError* error) {
49   CHECK(error->type() == ExtensionError::RUNTIME_ERROR);
50   return (static_cast<const RuntimeError*>(error))->stack_trace();
51 }
52 
53 // Verify that a given |frame| has the proper source and function name.
CheckStackFrame(const StackFrame & frame,const std::string & source,const std::string & function)54 void CheckStackFrame(const StackFrame& frame,
55                      const std::string& source,
56                      const std::string& function) {
57   EXPECT_EQ(base::UTF8ToUTF16(source), frame.source);
58   EXPECT_EQ(base::UTF8ToUTF16(function), frame.function);
59 }
60 
61 // Verify that all properties of a given |frame| are correct. Overloaded because
62 // we commonly do not check line/column numbers, as they are too likely
63 // to change.
CheckStackFrame(const StackFrame & frame,const std::string & source,const std::string & function,size_t line_number,size_t column_number)64 void CheckStackFrame(const StackFrame& frame,
65                      const std::string& source,
66                      const std::string& function,
67                      size_t line_number,
68                      size_t column_number) {
69   CheckStackFrame(frame, source, function);
70   EXPECT_EQ(line_number, frame.line_number);
71   EXPECT_EQ(column_number, frame.column_number);
72 }
73 
74 // 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)75 void CheckError(const ExtensionError* error,
76                 ExtensionError::Type type,
77                 const std::string& id,
78                 const std::string& source,
79                 bool from_incognito,
80                 const std::string& message) {
81   ASSERT_TRUE(error);
82   EXPECT_EQ(type, error->type());
83   EXPECT_EQ(id, error->extension_id());
84   EXPECT_EQ(base::UTF8ToUTF16(source), error->source());
85   EXPECT_EQ(from_incognito, error->from_incognito());
86   EXPECT_EQ(base::UTF8ToUTF16(message), error->message());
87 }
88 
89 // 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)90 void CheckRuntimeError(const ExtensionError* error,
91                        const std::string& id,
92                        const std::string& source,
93                        bool from_incognito,
94                        const std::string& message,
95                        logging::LogSeverity level,
96                        const GURL& context,
97                        size_t expected_stack_size) {
98   CheckError(error,
99              ExtensionError::RUNTIME_ERROR,
100              id,
101              source,
102              from_incognito,
103              message);
104 
105   const RuntimeError* runtime_error = static_cast<const RuntimeError*>(error);
106   EXPECT_EQ(level, runtime_error->level());
107   EXPECT_EQ(context, runtime_error->context_url());
108   EXPECT_EQ(expected_stack_size, runtime_error->stack_trace().size());
109 }
110 
CheckManifestError(const ExtensionError * error,const std::string & id,const std::string & message,const std::string & manifest_key,const std::string & manifest_specific)111 void CheckManifestError(const ExtensionError* error,
112                         const std::string& id,
113                         const std::string& message,
114                         const std::string& manifest_key,
115                         const std::string& manifest_specific) {
116   CheckError(error,
117              ExtensionError::MANIFEST_ERROR,
118              id,
119              // source is always the manifest for ManifestErrors.
120              base::FilePath(kManifestFilename).AsUTF8Unsafe(),
121              false,  // manifest errors are never from incognito.
122              message);
123 
124   const ManifestError* manifest_error =
125       static_cast<const ManifestError*>(error);
126   EXPECT_EQ(base::UTF8ToUTF16(manifest_key), manifest_error->manifest_key());
127   EXPECT_EQ(base::UTF8ToUTF16(manifest_specific),
128             manifest_error->manifest_specific());
129 }
130 
131 }  // namespace
132 
133 class ErrorConsoleBrowserTest : public ExtensionBrowserTest {
134  public:
ErrorConsoleBrowserTest()135   ErrorConsoleBrowserTest() : error_console_(nullptr) {}
~ErrorConsoleBrowserTest()136   ~ErrorConsoleBrowserTest() override {}
137 
138  protected:
139   // A helper class in order to wait for the proper number of errors to be
140   // caught by the ErrorConsole. This will run the MessageLoop until a given
141   // number of errors are observed.
142   // Usage:
143   //   ...
144   //   ErrorObserver observer(3, error_console);
145   //   <Cause three errors...>
146   //   observer.WaitForErrors();
147   //   <Perform any additional checks...>
148   class ErrorObserver : public ErrorConsole::Observer {
149    public:
ErrorObserver(size_t errors_expected,ErrorConsole * error_console)150     ErrorObserver(size_t errors_expected, ErrorConsole* error_console)
151         : errors_observed_(0),
152           errors_expected_(errors_expected),
153           waiting_(false),
154           error_console_(error_console) {
155       error_console_->AddObserver(this);
156     }
~ErrorObserver()157     virtual ~ErrorObserver() {
158       if (error_console_)
159         error_console_->RemoveObserver(this);
160     }
161 
162     // ErrorConsole::Observer implementation.
OnErrorAdded(const ExtensionError * error)163     void OnErrorAdded(const ExtensionError* error) override {
164       ++errors_observed_;
165       if (errors_observed_ >= errors_expected_) {
166         if (waiting_)
167           base::RunLoop::QuitCurrentWhenIdleDeprecated();
168       }
169     }
170 
OnErrorConsoleDestroyed()171     void OnErrorConsoleDestroyed() override { error_console_ = nullptr; }
172 
173     // Spin until the appropriate number of errors have been observed.
WaitForErrors()174     void WaitForErrors() {
175       if (errors_observed_ < errors_expected_) {
176         waiting_ = true;
177         content::RunMessageLoop();
178         waiting_ = false;
179       }
180     }
181 
182    private:
183     size_t errors_observed_;
184     size_t errors_expected_;
185     bool waiting_;
186 
187     ErrorConsole* error_console_;
188 
189     DISALLOW_COPY_AND_ASSIGN(ErrorObserver);
190   };
191 
192   // The type of action which we take after we load an extension in order to
193   // cause any errors.
194   enum Action {
195     // Navigate to a (non-chrome) page to allow a content script to run.
196     ACTION_NAVIGATE,
197     // Simulate a browser action click.
198     ACTION_BROWSER_ACTION,
199     // Navigate to the new tab page.
200     ACTION_NEW_TAB,
201     // Do nothing (errors will be caused by a background script,
202     // or by a manifest/loading warning).
203     ACTION_NONE
204   };
205 
SetUpOnMainThread()206   void SetUpOnMainThread() override {
207     ExtensionBrowserTest::SetUpOnMainThread();
208 
209     // Errors are only kept if we have Developer Mode enabled.
210     profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
211 
212     error_console_ = ErrorConsole::Get(profile());
213     CHECK(error_console_);
214 
215     test_data_dir_ = test_data_dir_.AppendASCII("error_console");
216   }
217 
GetTestURL()218   const GURL& GetTestURL() {
219     if (test_url_.is_empty()) {
220       CHECK(embedded_test_server()->Start());
221       test_url_ = embedded_test_server()->GetURL(kTestingPage);
222     }
223     return test_url_;
224   }
225 
226   // Load the extension at |path|, take the specified |action|, and wait for
227   // |expected_errors| errors. Populate |extension| with a pointer to the loaded
228   // extension.
LoadExtensionAndCheckErrors(const std::string & path,int flags,size_t errors_expected,Action action,const Extension ** extension)229   void LoadExtensionAndCheckErrors(
230       const std::string& path,
231       int flags,
232       size_t errors_expected,
233       Action action,
234       const Extension** extension) {
235     ErrorObserver observer(errors_expected, error_console_);
236     *extension =
237         LoadExtensionWithFlags(test_data_dir_.AppendASCII(path), flags);
238     ASSERT_TRUE(*extension);
239 
240     switch (action) {
241       case ACTION_NAVIGATE: {
242         ui_test_utils::NavigateToURL(browser(), GetTestURL());
243         break;
244       }
245       case ACTION_BROWSER_ACTION: {
246         ExtensionActionRunner::GetForWebContents(
247             browser()->tab_strip_model()->GetActiveWebContents())
248             ->RunAction(*extension, true);
249         break;
250       }
251       case ACTION_NEW_TAB: {
252         ui_test_utils::NavigateToURL(browser(),
253                                      GURL(chrome::kChromeUINewTabURL));
254         break;
255       }
256       case ACTION_NONE:
257         break;
258       default:
259         NOTREACHED();
260     }
261 
262     observer.WaitForErrors();
263 
264     // We should only have errors for a single extension, or should have no
265     // entries, if no errors were expected.
266     ASSERT_EQ(errors_expected > 0 ? 1u : 0u,
267               error_console()->get_num_entries_for_test());
268     ASSERT_EQ(
269         errors_expected,
270         error_console()->GetErrorsForExtension((*extension)->id()).size());
271   }
272 
error_console()273   ErrorConsole* error_console() { return error_console_; }
274 
275  private:
276   // The URL used in testing for simple page navigations.
277   GURL test_url_;
278 
279   // Weak reference to the ErrorConsole.
280   ErrorConsole* error_console_;
281 };
282 
283 // Test to ensure that we are successfully reporting manifest errors as an
284 // extension is installed.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,ReportManifestErrors)285 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, ReportManifestErrors) {
286   const Extension* extension = nullptr;
287   // We expect two errors - one for an invalid permission, and a second for
288   // an unknown key.
289   LoadExtensionAndCheckErrors("manifest_warnings", kFlagIgnoreManifestWarnings,
290                               2, ACTION_NONE, &extension);
291 
292   const ErrorList& errors =
293       error_console()->GetErrorsForExtension(extension->id());
294 
295   // Unfortunately, there's not always a hard guarantee of order in parsing the
296   // manifest, so there's not a definitive order in which these errors may
297   // occur. As such, we need to determine which error corresponds to which
298   // expected error.
299   const ExtensionError* permissions_error = nullptr;
300   const ExtensionError* unknown_key_error = nullptr;
301   const char kFakeKey[] = "not_a_real_key";
302   for (const auto& error : errors) {
303     ASSERT_EQ(ExtensionError::MANIFEST_ERROR, error->type());
304     std::string utf8_key = base::UTF16ToUTF8(
305         (static_cast<const ManifestError*>(error.get()))->manifest_key());
306     if (utf8_key == manifest_keys::kPermissions)
307       permissions_error = error.get();
308     else if (utf8_key == kFakeKey)
309       unknown_key_error = error.get();
310   }
311   ASSERT_TRUE(permissions_error);
312   ASSERT_TRUE(unknown_key_error);
313 
314   const char kFakePermission[] = "not_a_real_permission";
315   CheckManifestError(permissions_error,
316                      extension->id(),
317                      ErrorUtils::FormatErrorMessage(
318                          manifest_errors::kPermissionUnknownOrMalformed,
319                          kFakePermission),
320                      manifest_keys::kPermissions,
321                      kFakePermission);
322 
323   CheckManifestError(unknown_key_error,
324                      extension->id(),
325                      ErrorUtils::FormatErrorMessage(
326                          manifest_errors::kUnrecognizedManifestKey,
327                          kFakeKey),
328                      kFakeKey,
329                      std::string());
330 }
331 
332 // Test that we do not store any errors unless the Developer Mode switch is
333 // toggled on the profile.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,DontStoreErrorsWithoutDeveloperMode)334 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,
335                        DontStoreErrorsWithoutDeveloperMode) {
336   profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, false);
337 
338   const Extension* extension = nullptr;
339   // Same test as ReportManifestErrors, except we don't expect any errors since
340   // we disable Developer Mode.
341   LoadExtensionAndCheckErrors("manifest_warnings", kFlagIgnoreManifestWarnings,
342                               0, ACTION_NONE, &extension);
343 
344   // Now if we enable developer mode, the errors should be reported...
345   profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
346   EXPECT_EQ(2u, error_console()->GetErrorsForExtension(extension->id()).size());
347 
348   // ... and if we disable it again, all errors which we were holding should be
349   // removed.
350   profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, false);
351   EXPECT_EQ(0u, error_console()->GetErrorsForExtension(extension->id()).size());
352 }
353 
354 // Load an extension which, upon visiting any page, first sends out a console
355 // log, and then crashes with a JS TypeError.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,ContentScriptLogAndRuntimeError)356 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,
357                        ContentScriptLogAndRuntimeError) {
358   const Extension* extension = nullptr;
359   LoadExtensionAndCheckErrors(
360       "content_script_log_and_runtime_error",
361       kNoFlags,
362       2u,  // Two errors: A log message and a JS type error.
363       ACTION_NAVIGATE,
364       &extension);
365 
366   std::string script_url =
367       extension->GetResourceURL("content_script.js").spec();
368 
369   const ErrorList& errors =
370       error_console()->GetErrorsForExtension(extension->id());
371 
372   // The extension logs a message with console.log(), then another with
373   // console.warn(), and then triggers a TypeError.
374   // There should be exactly two errors (the warning and the TypeError). The
375   // error console ignores logs - this would tend to be too noisy, and doesn't
376   // jive with the big `ERRORS` button in the UI.
377   // See https://crbug.com/837401.
378   ASSERT_EQ(2u, errors.size());
379 
380   // The first error should be a console log.
381   CheckRuntimeError(errors[0].get(), extension->id(),
382                     script_url,  // The source should be the content script url.
383                     false,       // Not from incognito.
384                     "warned message",  // The error message is the log.
385                     logging::LOG_WARNING,
386                     GetTestURL(),  // Content scripts run in the web page.
387                     2u);
388 
389   const StackTrace& stack_trace1 = GetStackTraceFromError(errors[0].get());
390   CheckStackFrame(stack_trace1[0], script_url,
391                   "warnMessage",  // function name
392                   10u,            // line number
393                   11u /* column number */);
394 
395   CheckStackFrame(stack_trace1[1], script_url, kAnonymousFunction, 14u, 1u);
396 
397   // The second error should be a runtime error.
398   CheckRuntimeError(errors[1].get(), extension->id(), script_url,
399                     false,  // not from incognito
400                     "Uncaught TypeError: "
401                     "Cannot set property 'foo' of undefined",
402                     logging::LOG_ERROR,  // JS errors are always ERROR level.
403                     GetTestURL(), 1u);
404 
405   const StackTrace& stack_trace2 = GetStackTraceFromError(errors[1].get());
406   CheckStackFrame(stack_trace2[0], script_url, kAnonymousFunction, 17u, 1u);
407 }
408 
409 // Catch an error from a BrowserAction; this is more complex than a content
410 // script error, since browser actions are routed through our own code.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BrowserActionRuntimeError)411 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BrowserActionRuntimeError) {
412   const Extension* extension = nullptr;
413   LoadExtensionAndCheckErrors(
414       "browser_action_runtime_error",
415       kNoFlags,
416       1u,  // One error: A reference error from within the browser action.
417       ACTION_BROWSER_ACTION,
418       &extension);
419 
420   std::string script_url =
421       extension->GetResourceURL("browser_action.js").spec();
422 
423   const ErrorList& errors =
424       error_console()->GetErrorsForExtension(extension->id());
425 
426   // TODO(devlin): The specific event name (here, 'browserAction.onClicked')
427   // may or may not be worth preserving. In most cases, it's unnecessary with
428   // the line number, but it could be useful in some cases.
429   std::string message =
430       "Error in event handler: ReferenceError: baz is not defined";
431 
432   CheckRuntimeError(errors[0].get(), extension->id(), script_url,
433                     false,  // not incognito
434                     message, logging::LOG_ERROR,
435                     extension->GetResourceURL(kBackgroundPageName), 1u);
436 
437   const StackTrace& stack_trace = GetStackTraceFromError(errors[0].get());
438   // Note: This test used to have a stack trace of length 6 that contains stack
439   // frames in the extension code, but since crbug.com/404406 was fixed only
440   // stack frames within user-defined extension code are printed.
441 
442   CheckStackFrame(stack_trace[0], script_url, kAnonymousFunction);
443 }
444 
445 // Test that we can catch an error for calling an API with improper arguments.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BadAPIArgumentsRuntimeError)446 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIArgumentsRuntimeError) {
447   const Extension* extension = nullptr;
448   LoadExtensionAndCheckErrors(
449       "bad_api_arguments_runtime_error",
450       kNoFlags,
451       1,  // One error: call an API with improper arguments.
452       ACTION_NONE,
453       &extension);
454 
455   const ErrorList& errors =
456       error_console()->GetErrorsForExtension(extension->id());
457 
458   std::string source = extension->GetResourceURL("background.js").spec();
459   std::string message =
460       "Uncaught TypeError: Error in invocation of tabs.get"
461       "(integer tabId, function callback): No matching signature.";
462 
463   CheckRuntimeError(errors[0].get(), extension->id(), source,
464                     false,  // not incognito
465                     message, logging::LOG_ERROR,
466                     extension->GetResourceURL(kBackgroundPageName), 1u);
467 
468   const StackTrace& stack_trace = GetStackTraceFromError(errors[0].get());
469   ASSERT_EQ(1u, stack_trace.size());
470   CheckStackFrame(stack_trace[0], source, kAnonymousFunction);
471 }
472 
473 // Test that we catch an error when we try to call an API method without
474 // permission.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BadAPIPermissionsRuntimeError)475 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIPermissionsRuntimeError) {
476   const Extension* extension = nullptr;
477   LoadExtensionAndCheckErrors(
478       "bad_api_permissions_runtime_error",
479       kNoFlags,
480       1,  // One error: we try to call addUrl() on chrome.history without
481           // permission, which results in a TypeError.
482       ACTION_NONE,
483       &extension);
484 
485   std::string script_url = extension->GetResourceURL("background.js").spec();
486 
487   const ErrorList& errors =
488       error_console()->GetErrorsForExtension(extension->id());
489 
490   CheckRuntimeError(
491       errors[0].get(), extension->id(), script_url,
492       false,  // not incognito
493       "Uncaught TypeError: Cannot read property 'addUrl' of undefined",
494       logging::LOG_ERROR, extension->GetResourceURL(kBackgroundPageName), 1u);
495 
496   const StackTrace& stack_trace = GetStackTraceFromError(errors[0].get());
497   ASSERT_EQ(1u, stack_trace.size());
498   CheckStackFrame(stack_trace[0],
499                   script_url,
500                   kAnonymousFunction,
501                   5u, 1u);
502 }
503 
504 // Test that if there is an error in an HTML page loaded by an extension (most
505 // common with apps), it is caught and reported by the ErrorConsole.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,BadExtensionPage)506 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadExtensionPage) {
507   const Extension* extension = nullptr;
508   LoadExtensionAndCheckErrors(
509       "bad_extension_page",
510       kNoFlags,
511       1,  // One error: the page will load JS which has a reference error.
512       ACTION_NEW_TAB,
513       &extension);
514 }
515 
516 // Test that extension errors that go to chrome.runtime.lastError are caught
517 // and reported by the ErrorConsole.
IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest,CatchesLastError)518 IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, CatchesLastError) {
519   const Extension* extension = nullptr;
520   LoadExtensionAndCheckErrors(
521       "trigger_last_error",
522       kNoFlags,
523       1,  // One error, which is sent through last error when trying to remove
524           // a non-existent permisison.
525       ACTION_NONE,
526       &extension);
527 
528   const ErrorList& errors =
529       error_console()->GetErrorsForExtension(extension->id());
530   ASSERT_EQ(1u, errors.size());
531 
532   // TODO(devlin): This is unfortunate. We lose a lot of context by using
533   // RenderFrame::AddMessageToConsole() instead of console.error(). This could
534   // be expanded; blink::SourceLocation knows how to capture an inspector
535   // stack trace.
536   std::string source =
537       extension->GetResourceURL(kGeneratedBackgroundPageFilename).spec();
538   // Line number '0' comes from errors that are logged to the render frame
539   // directly (e.g. background_age.html (0)).
540   size_t line_number = 0;
541   // Column number remains at the default specified in StackFrame (1).
542   size_t column_number = 1;
543   std::string message =
544       "Unchecked runtime.lastError: 'foobar' is not a recognized permission.";
545 
546   CheckRuntimeError(errors[0].get(), extension->id(), source,
547                     false,  // not incognito
548                     message, logging::LOG_ERROR,
549                     extension->GetResourceURL(kBackgroundPageName), 1u);
550 
551   const StackTrace& stack_trace = GetStackTraceFromError(errors[0].get());
552   ASSERT_EQ(1u, stack_trace.size());
553   CheckStackFrame(stack_trace[0], source, kAnonymousFunction, line_number,
554                   column_number);
555 }
556 
557 }  // namespace extensions
558