1// Copyright (c) 2012 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/ui/webui/options/options_ui_browsertest.h"
6
7#include "base/prefs/pref_service.h"
8#include "base/scoped_observer.h"
9#include "base/strings/string16.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/browser/chrome_notification_types.h"
12#include "chrome/browser/signin/signin_manager_factory.h"
13#include "chrome/browser/ui/browser.h"
14#include "chrome/browser/ui/chrome_pages.h"
15#include "chrome/browser/ui/tabs/tab_strip_model.h"
16#include "chrome/browser/ui/webui/options/options_ui.h"
17#include "chrome/browser/ui/webui/uber/uber_ui.h"
18#include "chrome/common/pref_names.h"
19#include "chrome/common/url_constants.h"
20#include "chrome/grit/generated_resources.h"
21#include "chrome/test/base/ui_test_utils.h"
22#include "components/signin/core/browser/signin_manager.h"
23#include "content/public/browser/notification_service.h"
24#include "content/public/browser/render_frame_host.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/test/browser_test_utils.h"
27#include "content/public/test/test_utils.h"
28#include "ui/base/l10n/l10n_util.h"
29
30#if !defined(OS_CHROMEOS)
31#include <string>
32
33#include "base/basictypes.h"
34#include "base/bind.h"
35#include "base/callback.h"
36#include "base/files/file_path.h"
37#include "base/run_loop.h"
38#include "chrome/browser/browser_process.h"
39#include "chrome/browser/profiles/profile.h"
40#include "chrome/browser/profiles/profile_manager.h"
41#include "chrome/browser/ui/browser_commands.h"
42#include "content/public/test/test_navigation_observer.h"
43#include "ui/base/window_open_disposition.h"
44#include "url/gurl.h"
45#endif
46
47using content::MessageLoopRunner;
48
49namespace options {
50
51namespace {
52
53class SignOutWaiter : public SigninManagerBase::Observer {
54 public:
55  explicit SignOutWaiter(SigninManagerBase* signin_manager)
56      : seen_(false), running_(false), scoped_observer_(this) {
57    scoped_observer_.Add(signin_manager);
58  }
59  virtual ~SignOutWaiter() {}
60
61  void Wait() {
62    if (seen_)
63      return;
64
65    running_ = true;
66    message_loop_runner_ = new MessageLoopRunner;
67    message_loop_runner_->Run();
68    EXPECT_TRUE(seen_);
69  }
70
71  virtual void GoogleSignedOut(const std::string& account_id,
72                               const std::string& username) OVERRIDE {
73    seen_ = true;
74    if (!running_)
75      return;
76
77    message_loop_runner_->Quit();
78    running_ = false;
79  }
80
81 private:
82  bool seen_;
83  bool running_;
84  ScopedObserver<SigninManagerBase, SignOutWaiter> scoped_observer_;
85  scoped_refptr<MessageLoopRunner> message_loop_runner_;
86};
87
88#if !defined(OS_CHROMEOS)
89void RunClosureWhenProfileInitialized(const base::Closure& closure,
90                                      Profile* profile,
91                                      Profile::CreateStatus status) {
92  if (status == Profile::CREATE_STATUS_INITIALIZED)
93    closure.Run();
94}
95#endif
96
97bool FrameHasSettingsSourceHost(content::RenderFrameHost* frame) {
98  return frame->GetLastCommittedURL().DomainIs(
99      chrome::kChromeUISettingsFrameHost);
100}
101
102}  // namespace
103
104OptionsUIBrowserTest::OptionsUIBrowserTest() {
105}
106
107void OptionsUIBrowserTest::NavigateToSettings() {
108  NavigateToSettingsSubpage("");
109}
110
111void OptionsUIBrowserTest::NavigateToSettingsSubpage(
112    const std::string& sub_page) {
113  const GURL& url = chrome::GetSettingsUrl(sub_page);
114  ui_test_utils::NavigateToURLWithDisposition(browser(), url, CURRENT_TAB, 0);
115
116  content::WebContents* web_contents =
117      browser()->tab_strip_model()->GetActiveWebContents();
118  ASSERT_TRUE(web_contents);
119  ASSERT_TRUE(web_contents->GetWebUI());
120
121  content::WebUIController* controller =
122      web_contents->GetWebUI()->GetController();
123#if !defined(OS_CHROMEOS)
124  controller = static_cast<UberUI*>(controller)->
125      GetSubpage(chrome::kChromeUISettingsFrameURL)->GetController();
126#endif
127  OptionsUI* options_ui = static_cast<OptionsUI*>(controller);
128
129  // It is not possible to subscribe to the OnFinishedLoading event before the
130  // call to NavigateToURL(), because the WebUI does not yet exist at that time.
131  // However, it is safe to subscribe afterwards, because the event will always
132  // be posted asynchronously to the message loop.
133  scoped_refptr<MessageLoopRunner> message_loop_runner(new MessageLoopRunner);
134  scoped_ptr<OptionsUI::OnFinishedLoadingCallbackList::Subscription>
135      subscription = options_ui->RegisterOnFinishedLoadingCallback(
136          message_loop_runner->QuitClosure());
137  message_loop_runner->Run();
138
139  // The OnFinishedLoading event, which indicates that all WebUI initialization
140  // methods have been called on the JS side, is temporally unrelated to whether
141  // or not the WebContents considers itself to have finished loading. We want
142  // to wait for this too, however, because, e.g. this is a sufficient condition
143  // to get the focus properly placed on a form element.
144  content::WaitForLoadStop(web_contents);
145}
146
147void OptionsUIBrowserTest::NavigateToSettingsFrame() {
148  const GURL& url = GURL(chrome::kChromeUISettingsFrameURL);
149  ui_test_utils::NavigateToURL(browser(), url);
150}
151
152void OptionsUIBrowserTest::VerifyNavbar() {
153  bool navbar_exist = false;
154#if defined(OS_CHROMEOS)
155  bool should_navbar_exist = false;
156#else
157  bool should_navbar_exist = true;
158#endif
159  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
160      browser()->tab_strip_model()->GetActiveWebContents(),
161      "domAutomationController.send("
162      "    !!document.getElementById('navigation'))",
163      &navbar_exist));
164  EXPECT_EQ(should_navbar_exist, navbar_exist);
165}
166
167void OptionsUIBrowserTest::VerifyTitle() {
168  base::string16 title =
169      browser()->tab_strip_model()->GetActiveWebContents()->GetTitle();
170  base::string16 expected_title = l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE);
171  EXPECT_NE(title.find(expected_title), base::string16::npos);
172}
173
174content::RenderFrameHost* OptionsUIBrowserTest::GetSettingsFrame() {
175  // NB: The utility function content::FrameHasSourceUrl can't be used because
176  // the settings frame navigates itself to chrome://settings-frame/settings
177  // to indicate that it's showing the top-level settings. Therefore, just
178  // match the host.
179  return content::FrameMatchingPredicate(
180      browser()->tab_strip_model()->GetActiveWebContents(),
181      base::Bind(&FrameHasSettingsSourceHost));
182}
183
184IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, LoadOptionsByURL) {
185  NavigateToSettings();
186  VerifyTitle();
187  VerifyNavbar();
188}
189
190// Flaky on win_rel when the profile is deleted crbug.com/103355
191// Also related to crbug.com/104851
192#if defined(OS_WIN)
193#define MAYBE_VerifyManagedSignout DISABLED_VerifyManagedSignout
194#else
195#define MAYBE_VerifyManagedSignout VerifyManagedSignout
196#endif
197
198#if !defined(OS_CHROMEOS)
199IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, MAYBE_VerifyManagedSignout) {
200  SigninManager* signin =
201      SigninManagerFactory::GetForProfile(browser()->profile());
202  signin->OnExternalSigninCompleted("test@example.com");
203  signin->ProhibitSignout(true);
204
205  NavigateToSettingsFrame();
206
207  // This script simulates a click on the "Disconnect your Google Account"
208  // button and returns true if the hidden flag of the appropriate dialog gets
209  // flipped.
210  bool result = false;
211  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
212      browser()->tab_strip_model()->GetActiveWebContents(),
213      "var dialog = $('manage-profile-overlay-disconnect-managed');"
214      "var original_status = dialog.hidden;"
215      "var original = ManageProfileOverlay.showDisconnectManagedProfileDialog;"
216      "var teststub = function(event) {"
217      "  original(event);"
218      "  domAutomationController.send(original_status && !dialog.hidden);"
219      "};"
220      "ManageProfileOverlay.showDisconnectManagedProfileDialog = teststub;"
221      "$('start-stop-sync').click();",
222      &result));
223
224  EXPECT_TRUE(result);
225
226  base::FilePath profile_dir = browser()->profile()->GetPath();
227  ProfileInfoCache& profile_info_cache =
228      g_browser_process->profile_manager()->GetProfileInfoCache();
229
230  EXPECT_TRUE(DirectoryExists(profile_dir));
231  EXPECT_TRUE(profile_info_cache.GetIndexOfProfileWithPath(profile_dir) !=
232              std::string::npos);
233
234  content::WindowedNotificationObserver wait_for_profile_deletion(
235      chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
236      content::NotificationService::AllSources());
237
238  // TODO(kaliamoorthi): Get the macos problem fixed and remove this code.
239  // Deleting the Profile also destroys all browser windows of that Profile.
240  // Wait for the current browser to close before resuming, otherwise
241  // the browser_tests shutdown code will be confused on the Mac.
242  content::WindowedNotificationObserver wait_for_browser_closed(
243      chrome::NOTIFICATION_BROWSER_CLOSED,
244      content::NotificationService::AllSources());
245
246  ASSERT_TRUE(content::ExecuteScript(
247      browser()->tab_strip_model()->GetActiveWebContents(),
248      "$('disconnect-managed-profile-ok').click();"));
249
250  wait_for_profile_deletion.Wait();
251
252  EXPECT_TRUE(profile_info_cache.GetIndexOfProfileWithPath(profile_dir) ==
253              std::string::npos);
254
255  wait_for_browser_closed.Wait();
256}
257
258IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, VerifyUnmanagedSignout) {
259  SigninManager* signin =
260      SigninManagerFactory::GetForProfile(browser()->profile());
261  const std::string user = "test@example.com";
262  signin->OnExternalSigninCompleted(user);
263
264  NavigateToSettingsFrame();
265
266  // This script simulates a click on the "Disconnect your Google Account"
267  // button and returns true if the hidden flag of the appropriate dialog gets
268  // flipped.
269  bool result = false;
270  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
271      browser()->tab_strip_model()->GetActiveWebContents(),
272      "var dialog = $('sync-setup-stop-syncing');"
273      "var original_status = dialog.hidden;"
274      "$('start-stop-sync').click();"
275      "domAutomationController.send(original_status && !dialog.hidden);",
276      &result));
277
278  EXPECT_TRUE(result);
279
280  SignOutWaiter sign_out_waiter(signin);
281
282  ASSERT_TRUE(content::ExecuteScript(
283      browser()->tab_strip_model()->GetActiveWebContents(),
284      "$('stop-syncing-ok').click();"));
285
286  sign_out_waiter.Wait();
287
288  EXPECT_TRUE(browser()->profile()->GetProfileName() != user);
289  EXPECT_FALSE(signin->IsAuthenticated());
290}
291
292// Regression test for http://crbug.com/301436, excluded on Chrome OS because
293// profile management in the settings UI exists on desktop platforms only.
294IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, NavigateBackFromOverlayDialog) {
295  NavigateToSettingsFrame();
296
297  // Click a button that opens an overlay dialog.
298  content::WebContents* contents =
299      browser()->tab_strip_model()->GetActiveWebContents();
300  ASSERT_TRUE(content::ExecuteScript(
301      contents, "$('manage-default-search-engines').click();"));
302
303  // Go back to the settings page.
304  content::TestNavigationObserver observer(contents);
305  chrome::GoBack(browser(), CURRENT_TAB);
306  observer.Wait();
307
308  // Verify that the settings page lists one profile.
309  const char javascript[] =
310      "domAutomationController.send("
311      "    document.querySelectorAll('list#profiles-list > div[role=listitem]')"
312      "        .length);";
313  int profiles;
314  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
315      contents, javascript, &profiles));
316  EXPECT_EQ(1, profiles);
317
318  // Create a second profile.
319  ProfileManager* profile_manager = g_browser_process->profile_manager();
320  const base::FilePath profile_path =
321      profile_manager->GenerateNextProfileDirectoryPath();
322
323  base::RunLoop run_loop;
324  profile_manager->CreateProfileAsync(
325      profile_manager->GenerateNextProfileDirectoryPath(),
326      base::Bind(&RunClosureWhenProfileInitialized,
327                 run_loop.QuitClosure()),
328                 base::string16(),
329                 base::string16(),
330                 std::string());
331  run_loop.Run();
332
333  // Verify that the settings page has updated and lists two profiles.
334  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
335      contents, javascript, &profiles));
336  EXPECT_EQ(2, profiles);
337}
338#endif
339
340}  // namespace options
341