1// Copyright (c) 2011 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 "base/command_line.h"
6#include "base/path_service.h"
7#include "base/string_util.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/browser/debugger/devtools_client_host.h"
10#include "chrome/browser/debugger/devtools_manager.h"
11#include "chrome/browser/debugger/devtools_window.h"
12#include "chrome/browser/extensions/extension_host.h"
13#include "chrome/browser/extensions/extension_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/common/chrome_paths.h"
17#include "chrome/test/in_process_browser_test.h"
18#include "chrome/test/ui_test_utils.h"
19#include "content/browser/renderer_host/render_view_host.h"
20#include "content/browser/tab_contents/tab_contents.h"
21#include "content/common/notification_registrar.h"
22#include "content/common/notification_service.h"
23#include "net/test/test_server.h"
24
25namespace {
26
27// Used to block until a dev tools client window's browser is closed.
28class BrowserClosedObserver : public NotificationObserver {
29 public:
30  explicit BrowserClosedObserver(Browser* browser) {
31    registrar_.Add(this, NotificationType::BROWSER_CLOSED,
32                   Source<Browser>(browser));
33    ui_test_utils::RunMessageLoop();
34  }
35
36  virtual void Observe(NotificationType type,
37                       const NotificationSource& source,
38                       const NotificationDetails& details) {
39    MessageLoopForUI::current()->Quit();
40  }
41
42 private:
43  NotificationRegistrar registrar_;
44  DISALLOW_COPY_AND_ASSIGN(BrowserClosedObserver);
45};
46
47// The delay waited in some cases where we don't have a notifications for an
48// action we take.
49const int kActionDelayMs = 500;
50
51const char kDebuggerTestPage[] = "files/devtools/debugger_test_page.html";
52const char kHeapProfilerPage[] = "files/devtools/heap_profiler.html";
53const char kPauseWhenLoadingDevTools[] =
54    "files/devtools/pause_when_loading_devtools.html";
55const char kPauseWhenScriptIsRunning[] =
56    "files/devtools/pause_when_script_is_running.html";
57const char kPageWithContentScript[] =
58    "files/devtools/page_with_content_script.html";
59
60
61class DevToolsSanityTest : public InProcessBrowserTest {
62 public:
63  DevToolsSanityTest() {
64    set_show_window(true);
65    EnableDOMAutomation();
66  }
67
68 protected:
69  void RunTest(const std::string& test_name, const std::string& test_page) {
70    OpenDevToolsWindow(test_page);
71    std::string result;
72
73    // At first check that JavaScript part of the front-end is loaded by
74    // checking that global variable uiTests exists(it's created after all js
75    // files have been loaded) and has runTest method.
76    ASSERT_TRUE(
77        ui_test_utils::ExecuteJavaScriptAndExtractString(
78            client_contents_->render_view_host(),
79            L"",
80            L"window.domAutomationController.send("
81            L"'' + (window.uiTests && (typeof uiTests.runTest)));",
82            &result));
83
84    if (result == "function") {
85      ASSERT_TRUE(
86          ui_test_utils::ExecuteJavaScriptAndExtractString(
87              client_contents_->render_view_host(),
88              L"",
89              UTF8ToWide(StringPrintf("uiTests.runTest('%s')",
90                                      test_name.c_str())),
91              &result));
92      EXPECT_EQ("[OK]", result);
93    } else {
94      FAIL() << "DevTools front-end is broken.";
95    }
96    CloseDevToolsWindow();
97  }
98
99  void OpenDevToolsWindow(const std::string& test_page) {
100    ASSERT_TRUE(test_server()->Start());
101    GURL url = test_server()->GetURL(test_page);
102    ui_test_utils::NavigateToURL(browser(), url);
103
104    inspected_rvh_ = GetInspectedTab()->render_view_host();
105    DevToolsManager* devtools_manager = DevToolsManager::GetInstance();
106    devtools_manager->OpenDevToolsWindow(inspected_rvh_);
107
108    DevToolsClientHost* client_host =
109        devtools_manager->GetDevToolsClientHostFor(inspected_rvh_);
110    window_ = client_host->AsDevToolsWindow();
111    RenderViewHost* client_rvh = window_->GetRenderViewHost();
112    client_contents_ = client_rvh->delegate()->GetAsTabContents();
113    ui_test_utils::WaitForNavigation(&client_contents_->controller());
114  }
115
116  TabContents* GetInspectedTab() {
117    return browser()->GetTabContentsAt(0);
118  }
119
120  void CloseDevToolsWindow() {
121    DevToolsManager* devtools_manager = DevToolsManager::GetInstance();
122    // UnregisterDevToolsClientHostFor may destroy window_ so store the browser
123    // first.
124    Browser* browser = window_->browser();
125    devtools_manager->UnregisterDevToolsClientHostFor(inspected_rvh_);
126
127    // Wait only when DevToolsWindow has a browser. For docked DevTools, this
128    // is NULL and we skip the wait.
129    if (browser)
130      BrowserClosedObserver close_observer(browser);
131  }
132
133  TabContents* client_contents_;
134  DevToolsWindow* window_;
135  RenderViewHost* inspected_rvh_;
136};
137
138
139class CancelableQuitTask : public Task {
140 public:
141  explicit CancelableQuitTask(const std::string& timeout_message)
142      : timeout_message_(timeout_message),
143        cancelled_(false) {
144  }
145
146  void cancel() {
147    cancelled_ = true;
148  }
149
150  virtual void Run() {
151    if (cancelled_) {
152      return;
153    }
154    FAIL() << timeout_message_;
155    MessageLoop::current()->Quit();
156  }
157
158 private:
159  std::string timeout_message_;
160  bool cancelled_;
161};
162
163
164// Base class for DevTools tests that test devtools functionality for
165// extensions and content scripts.
166class DevToolsExtensionDebugTest : public DevToolsSanityTest,
167                                   public NotificationObserver {
168 public:
169  DevToolsExtensionDebugTest() : DevToolsSanityTest() {
170    PathService::Get(chrome::DIR_TEST_DATA, &test_extensions_dir_);
171    test_extensions_dir_ = test_extensions_dir_.AppendASCII("devtools");
172    test_extensions_dir_ = test_extensions_dir_.AppendASCII("extensions");
173  }
174
175 protected:
176  // Load an extention from test\data\devtools\extensions\<extension_name>
177  void LoadExtension(const char* extension_name) {
178    FilePath path = test_extensions_dir_.AppendASCII(extension_name);
179    ASSERT_TRUE(LoadExtensionFromPath(path)) << "Failed to load extension.";
180  }
181
182 private:
183  bool LoadExtensionFromPath(const FilePath& path) {
184    ExtensionService* service = browser()->profile()->GetExtensionService();
185    size_t num_before = service->extensions()->size();
186    {
187      NotificationRegistrar registrar;
188      registrar.Add(this, NotificationType::EXTENSION_LOADED,
189                    NotificationService::AllSources());
190      CancelableQuitTask* delayed_quit =
191          new CancelableQuitTask("Extension load timed out.");
192      MessageLoop::current()->PostDelayedTask(FROM_HERE, delayed_quit,
193          4*1000);
194      service->LoadExtension(path);
195      ui_test_utils::RunMessageLoop();
196      delayed_quit->cancel();
197    }
198    size_t num_after = service->extensions()->size();
199    if (num_after != (num_before + 1))
200      return false;
201
202    return WaitForExtensionHostsToLoad();
203  }
204
205  bool WaitForExtensionHostsToLoad() {
206    // Wait for all the extension hosts that exist to finish loading.
207    // NOTE: This assumes that the extension host list is not changing while
208    // this method is running.
209
210    NotificationRegistrar registrar;
211    registrar.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
212                  NotificationService::AllSources());
213    CancelableQuitTask* delayed_quit =
214        new CancelableQuitTask("Extension host load timed out.");
215    MessageLoop::current()->PostDelayedTask(FROM_HERE, delayed_quit,
216        4*1000);
217
218    ExtensionProcessManager* manager =
219          browser()->profile()->GetExtensionProcessManager();
220    for (ExtensionProcessManager::const_iterator iter = manager->begin();
221         iter != manager->end();) {
222      if ((*iter)->did_stop_loading())
223        ++iter;
224      else
225        ui_test_utils::RunMessageLoop();
226    }
227
228    delayed_quit->cancel();
229    return true;
230  }
231
232  void Observe(NotificationType type,
233               const NotificationSource& source,
234               const NotificationDetails& details) {
235    switch (type.value) {
236      case NotificationType::EXTENSION_LOADED:
237      case NotificationType::EXTENSION_HOST_DID_STOP_LOADING:
238        MessageLoopForUI::current()->Quit();
239        break;
240      default:
241        NOTREACHED();
242        break;
243    }
244  }
245
246  FilePath test_extensions_dir_;
247};
248
249// Tests heap profiler.
250IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, FAILS_TestHeapProfiler) {
251  RunTest("testHeapProfiler", kHeapProfilerPage);
252}
253
254// Tests scripts panel showing.
255IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestShowScriptsTab) {
256  RunTest("testShowScriptsTab", kDebuggerTestPage);
257}
258
259// Tests that scripts tab is populated with inspected scripts even if it
260// hadn't been shown by the moment inspected paged refreshed.
261// @see http://crbug.com/26312
262IN_PROC_BROWSER_TEST_F(DevToolsSanityTest,
263                       TestScriptsTabIsPopulatedOnInspectedPageRefresh) {
264  // Clear inspector settings to ensure that Elements will be
265  // current panel when DevTools window is open.
266  GetInspectedTab()->render_view_host()->delegate()->ClearInspectorSettings();
267  RunTest("testScriptsTabIsPopulatedOnInspectedPageRefresh",
268          kDebuggerTestPage);
269}
270
271// Tests that a content script is in the scripts list.
272// This test is disabled, see bug 28961.
273IN_PROC_BROWSER_TEST_F(DevToolsExtensionDebugTest,
274                       TestContentScriptIsPresent) {
275  LoadExtension("simple_content_script");
276  RunTest("testContentScriptIsPresent", kPageWithContentScript);
277}
278
279// Tests that scripts are not duplicated after Scripts Panel switch.
280IN_PROC_BROWSER_TEST_F(DevToolsSanityTest,
281                       TestNoScriptDuplicatesOnPanelSwitch) {
282  RunTest("testNoScriptDuplicatesOnPanelSwitch", kDebuggerTestPage);
283}
284
285// Tests that debugger works correctly if pause event occurs when DevTools
286// frontend is being loaded.
287IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestPauseWhenLoadingDevTools) {
288  RunTest("testPauseWhenLoadingDevTools", kPauseWhenLoadingDevTools);
289}
290
291// Tests that pressing 'Pause' will pause script execution if the script
292// is already running.
293IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestPauseWhenScriptIsRunning) {
294  RunTest("testPauseWhenScriptIsRunning", kPauseWhenScriptIsRunning);
295}
296
297}  // namespace
298