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