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