Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / error_console / error_console_browsertest.cc
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
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.
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.
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.
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.
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
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:
129   ErrorConsoleBrowserTest() : error_console_(NULL) { }
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:
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     }
151     virtual ~ErrorObserver() {
152       if (error_console_)
153         error_console_->RemoveObserver(this);
154     }
155
156     // ErrorConsole::Observer implementation.
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
165     virtual void OnErrorConsoleDestroyed() OVERRIDE {
166       error_console_ = NULL;
167     }
168
169     // Spin until the appropriate number of errors have been observed.
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
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
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
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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