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/set_as_default_browser_ui.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/memory/weak_ptr.h" 10 #include "base/metrics/histogram.h" 11 #include "base/path_service.h" 12 #include "base/prefs/pref_service.h" 13 #include "base/win/win_util.h" 14 #include "chrome/browser/first_run/first_run.h" 15 #include "chrome/browser/lifetime/application_lifetime.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/shell_integration.h" 18 #include "chrome/browser/ui/browser.h" 19 #include "chrome/browser/ui/browser_dialogs.h" 20 #include "chrome/browser/ui/browser_finder.h" 21 #include "chrome/browser/ui/browser_list.h" 22 #include "chrome/browser/ui/browser_list_observer.h" 23 #include "chrome/browser/ui/browser_window.h" 24 #include "chrome/browser/ui/chrome_pages.h" 25 #include "chrome/browser/ui/singleton_tabs.h" 26 #include "chrome/browser/ui/sync/sync_promo_ui.h" 27 #include "chrome/browser/ui/tabs/tab_strip_model.h" 28 #include "chrome/common/pref_names.h" 29 #include "chrome/common/url_constants.h" 30 #include "chrome/installer/util/install_util.h" 31 #include "content/public/browser/browser_thread.h" 32 #include "content/public/browser/web_contents.h" 33 #include "content/public/browser/web_contents_delegate.h" 34 #include "content/public/browser/web_contents_view.h" 35 #include "content/public/browser/web_ui.h" 36 #include "content/public/browser/web_ui_data_source.h" 37 #include "content/public/browser/web_ui_message_handler.h" 38 #include "grit/browser_resources.h" 39 #include "grit/generated_resources.h" 40 #include "grit/locale_settings.h" 41 #include "ui/base/l10n/l10n_font_util.h" 42 #include "ui/base/l10n/l10n_util.h" 43 #include "ui/gfx/font.h" 44 #include "ui/views/widget/widget.h" 45 #include "ui/web_dialogs/web_dialog_delegate.h" 46 47 using content::BrowserThread; 48 using content::WebContents; 49 using content::WebUIMessageHandler; 50 51 namespace { 52 53 const char kSetAsDefaultBrowserHistogram[] = "DefaultBrowser.InteractionResult"; 54 55 // The enum permits registering in UMA the three possible outcomes. 56 // ACCEPTED: user pressed Next and made Chrome default. 57 // DECLINED: user simply closed the dialog without making Chrome default. 58 // REGRETTED: user pressed Next but then elected a different default browser. 59 // ACCEPTED_IMMERSE: as above with a switch to metro mode. 60 enum MakeChromeDefaultResult { 61 MAKE_CHROME_DEFAULT_ACCEPTED, 62 MAKE_CHROME_DEFAULT_DECLINED, 63 MAKE_CHROME_DEFAULT_REGRETTED, 64 MAKE_CHROME_DEFAULT_ACCEPTED_IMMERSE, 65 MAKE_CHROME_DEFAULT_MAX 66 }; 67 68 content::WebUIDataSource* CreateSetAsDefaultBrowserUIHTMLSource() { 69 content::WebUIDataSource* data_source = content::WebUIDataSource::Create( 70 chrome::kChromeUIMetroFlowHost); 71 data_source->AddLocalizedString("page-title", IDS_METRO_FLOW_TAB_TITLE); 72 data_source->AddLocalizedString("flowTitle", IDS_METRO_FLOW_TITLE_SHORT); 73 data_source->AddLocalizedString("flowDescription", 74 IDS_METRO_FLOW_DESCRIPTION); 75 data_source->AddLocalizedString("flowNext", 76 IDS_METRO_FLOW_SET_DEFAULT); 77 data_source->AddLocalizedString("chromeLogoString", 78 IDS_METRO_FLOW_LOGO_STRING_ALT); 79 data_source->SetJsonPath("strings.js"); 80 data_source->AddResourcePath("set_as_default_browser.js", 81 IDR_SET_AS_DEFAULT_BROWSER_JS); 82 data_source->SetDefaultResource(IDR_SET_AS_DEFAULT_BROWSER_HTML); 83 return data_source; 84 } 85 86 // A simple class serving as a delegate for passing down the result of the 87 // interaction. 88 class ResponseDelegate { 89 public: 90 virtual void SetDialogInteractionResult(MakeChromeDefaultResult result) = 0; 91 92 protected: 93 virtual ~ResponseDelegate() { } 94 }; 95 96 // Event handler for SetAsDefaultBrowserUI. Capable of setting Chrome as the 97 // default browser on button click, closing itself and triggering Chrome 98 // restart. 99 class SetAsDefaultBrowserHandler 100 : public WebUIMessageHandler, 101 public base::SupportsWeakPtr<SetAsDefaultBrowserHandler>, 102 public ShellIntegration::DefaultWebClientObserver { 103 public: 104 explicit SetAsDefaultBrowserHandler( 105 const base::WeakPtr<ResponseDelegate>& response_delegate); 106 virtual ~SetAsDefaultBrowserHandler(); 107 108 // WebUIMessageHandler implementation. 109 virtual void RegisterMessages() OVERRIDE; 110 111 // ShellIntegration::DefaultWebClientObserver implementation. 112 virtual void SetDefaultWebClientUIState( 113 ShellIntegration::DefaultWebClientUIState state) OVERRIDE; 114 virtual void OnSetAsDefaultConcluded(bool close_chrome) OVERRIDE; 115 virtual bool IsInteractiveSetDefaultPermitted() OVERRIDE; 116 117 private: 118 // Handler for the 'Next' (or 'make Chrome the Metro browser') button. 119 void HandleLaunchSetDefaultBrowserFlow(const ListValue* args); 120 121 // Close this web ui. 122 void ConcludeInteraction(MakeChromeDefaultResult interaction_result); 123 124 // Returns true if Chrome should be restarted in immersive mode upon being 125 // made the default browser. 126 bool ShouldAttemptImmersiveRestart(); 127 128 scoped_refptr<ShellIntegration::DefaultBrowserWorker> default_browser_worker_; 129 bool set_default_returned_; 130 bool set_default_result_; 131 base::WeakPtr<ResponseDelegate> response_delegate_; 132 133 DISALLOW_COPY_AND_ASSIGN(SetAsDefaultBrowserHandler); 134 }; 135 136 SetAsDefaultBrowserHandler::SetAsDefaultBrowserHandler( 137 const base::WeakPtr<ResponseDelegate>& response_delegate) 138 : default_browser_worker_(new ShellIntegration::DefaultBrowserWorker(this)), 139 set_default_returned_(false), set_default_result_(false), 140 response_delegate_(response_delegate) { 141 } 142 143 SetAsDefaultBrowserHandler::~SetAsDefaultBrowserHandler() { 144 default_browser_worker_->ObserverDestroyed(); 145 } 146 147 void SetAsDefaultBrowserHandler::RegisterMessages() { 148 web_ui()->RegisterMessageCallback( 149 "SetAsDefaultBrowser:LaunchSetDefaultBrowserFlow", 150 base::Bind(&SetAsDefaultBrowserHandler::HandleLaunchSetDefaultBrowserFlow, 151 base::Unretained(this))); 152 } 153 154 void SetAsDefaultBrowserHandler::SetDefaultWebClientUIState( 155 ShellIntegration::DefaultWebClientUIState state) { 156 // The callback is expected to be invoked once the procedure has completed. 157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 158 if (!set_default_returned_) 159 return; 160 161 if (state == ShellIntegration::STATE_NOT_DEFAULT && set_default_result_) { 162 // The operation concluded, but Chrome is still not the default. 163 // If the call has succeeded, this suggests user has decided not to make 164 // chrome the default. 165 ConcludeInteraction(MAKE_CHROME_DEFAULT_REGRETTED); 166 } else if (state == ShellIntegration::STATE_IS_DEFAULT) { 167 ConcludeInteraction(ShouldAttemptImmersiveRestart() ? 168 MAKE_CHROME_DEFAULT_ACCEPTED_IMMERSE : MAKE_CHROME_DEFAULT_ACCEPTED); 169 } 170 171 // Otherwise, keep the dialog open since the user probably didn't make a 172 // choice. 173 } 174 175 void SetAsDefaultBrowserHandler::OnSetAsDefaultConcluded(bool call_result) { 176 set_default_returned_ = true; 177 set_default_result_ = call_result; 178 } 179 180 bool SetAsDefaultBrowserHandler::IsInteractiveSetDefaultPermitted() { 181 return true; 182 } 183 184 void SetAsDefaultBrowserHandler::HandleLaunchSetDefaultBrowserFlow( 185 const ListValue* args) { 186 set_default_returned_ = false; 187 set_default_result_ = false; 188 default_browser_worker_->StartSetAsDefault(); 189 } 190 191 void SetAsDefaultBrowserHandler::ConcludeInteraction( 192 MakeChromeDefaultResult interaction_result) { 193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 194 195 if (response_delegate_) 196 response_delegate_->SetDialogInteractionResult(interaction_result); 197 198 WebContents* contents = web_ui()->GetWebContents(); 199 200 if (contents) { 201 content::WebContentsDelegate* delegate = contents->GetDelegate(); 202 if (delegate) 203 delegate->CloseContents(contents); 204 } 205 } 206 207 bool SetAsDefaultBrowserHandler::ShouldAttemptImmersiveRestart() { 208 return (base::win::IsTouchEnabledDevice() && 209 !Profile::FromWebUI(web_ui())->GetPrefs()->GetBoolean( 210 prefs::kSuppressSwitchToMetroModeOnSetDefault)); 211 } 212 213 // A web dialog delegate implementation for when 'Make Chrome Metro' UI 214 // is displayed on a dialog. 215 class SetAsDefaultBrowserDialogImpl : public ui::WebDialogDelegate, 216 public ResponseDelegate, 217 public chrome::BrowserListObserver { 218 public: 219 SetAsDefaultBrowserDialogImpl(Profile* profile, Browser* browser); 220 virtual ~SetAsDefaultBrowserDialogImpl(); 221 // Show a modal web dialog with kChromeUIMetroFlowURL page. 222 void ShowDialog(); 223 224 protected: 225 // Overridden from WebDialogDelegate: 226 virtual ui::ModalType GetDialogModalType() const OVERRIDE; 227 virtual string16 GetDialogTitle() const OVERRIDE; 228 virtual GURL GetDialogContentURL() const OVERRIDE; 229 virtual void GetWebUIMessageHandlers( 230 std::vector<WebUIMessageHandler*>* handlers) const OVERRIDE; 231 virtual void GetDialogSize(gfx::Size* size) const OVERRIDE; 232 virtual std::string GetDialogArgs() const OVERRIDE; 233 virtual void OnDialogClosed(const std::string& json_retval) OVERRIDE; 234 virtual void OnCloseContents(WebContents* source, 235 bool* out_close_dialog) OVERRIDE; 236 virtual bool ShouldShowDialogTitle() const OVERRIDE; 237 virtual bool HandleContextMenu( 238 const content::ContextMenuParams& params) OVERRIDE; 239 240 // Overridden from ResponseDelegate: 241 virtual void SetDialogInteractionResult(MakeChromeDefaultResult result); 242 243 // Overridden from BrowserListObserver: 244 virtual void OnBrowserRemoved(Browser* browser) OVERRIDE; 245 246 private: 247 // Reset the first-run sentinel file, so must be called on the FILE thread. 248 // This is needed if the browser should be restarted in immersive mode. 249 // The method is static because the dialog could be destroyed 250 // before the task arrives on the FILE thread. 251 static void AttemptImmersiveFirstRunRestartOnFileThread(); 252 253 Profile* profile_; 254 Browser* browser_; 255 mutable bool owns_handler_; 256 base::WeakPtrFactory<ResponseDelegate> response_delegate_ptr_factory_; 257 SetAsDefaultBrowserHandler* handler_; 258 MakeChromeDefaultResult dialog_interaction_result_; 259 260 DISALLOW_COPY_AND_ASSIGN(SetAsDefaultBrowserDialogImpl); 261 }; 262 263 SetAsDefaultBrowserDialogImpl::SetAsDefaultBrowserDialogImpl(Profile* profile, 264 Browser* browser) 265 : profile_(profile), 266 browser_(browser), 267 owns_handler_(true), 268 response_delegate_ptr_factory_(this), 269 handler_(new SetAsDefaultBrowserHandler( 270 response_delegate_ptr_factory_.GetWeakPtr())), 271 dialog_interaction_result_(MAKE_CHROME_DEFAULT_DECLINED) { 272 BrowserList::AddObserver(this); 273 } 274 275 SetAsDefaultBrowserDialogImpl::~SetAsDefaultBrowserDialogImpl() { 276 if (browser_) 277 BrowserList::RemoveObserver(this); 278 if (owns_handler_) 279 delete handler_; 280 } 281 282 void SetAsDefaultBrowserDialogImpl::ShowDialog() { 283 // Use a NULL parent window to make sure that the dialog will have an item 284 // in the Windows task bar. The code below will make it highlight if the 285 // dialog is not in the foreground. 286 gfx::NativeWindow native_window = chrome::ShowWebDialog(NULL, profile_, this); 287 views::Widget* widget = views::Widget::GetWidgetForNativeWindow( 288 native_window); 289 widget->FlashFrame(true); 290 } 291 292 ui::ModalType SetAsDefaultBrowserDialogImpl::GetDialogModalType() const { 293 return ui::MODAL_TYPE_SYSTEM; 294 } 295 296 string16 SetAsDefaultBrowserDialogImpl::GetDialogTitle() const { 297 return l10n_util::GetStringUTF16(IDS_METRO_FLOW_TAB_TITLE); 298 } 299 300 GURL SetAsDefaultBrowserDialogImpl::GetDialogContentURL() const { 301 std::string url_string(chrome::kChromeUIMetroFlowURL); 302 return GURL(url_string); 303 } 304 305 void SetAsDefaultBrowserDialogImpl::GetWebUIMessageHandlers( 306 std::vector<WebUIMessageHandler*>* handlers) const { 307 handlers->push_back(handler_); 308 owns_handler_ = false; 309 } 310 311 void SetAsDefaultBrowserDialogImpl::GetDialogSize(gfx::Size* size) const { 312 PrefService* prefs = profile_->GetPrefs(); 313 gfx::Font approximate_web_font( 314 prefs->GetString(prefs::kWebKitSansSerifFontFamily), 315 prefs->GetInteger(prefs::kWebKitDefaultFontSize)); 316 317 *size = ui::GetLocalizedContentsSizeForFont( 318 IDS_METRO_FLOW_WIDTH_CHARS, IDS_METRO_FLOW_HEIGHT_LINES, 319 approximate_web_font); 320 } 321 322 std::string SetAsDefaultBrowserDialogImpl::GetDialogArgs() const { 323 return "[]"; 324 } 325 326 void SetAsDefaultBrowserDialogImpl::OnDialogClosed( 327 const std::string& json_retval) { 328 // Register the user's response in UMA. 329 UMA_HISTOGRAM_ENUMERATION(kSetAsDefaultBrowserHistogram, 330 dialog_interaction_result_, 331 MAKE_CHROME_DEFAULT_MAX); 332 333 if (dialog_interaction_result_ == MAKE_CHROME_DEFAULT_ACCEPTED_IMMERSE) { 334 BrowserThread::PostTask( 335 BrowserThread::FILE, FROM_HERE, 336 base::Bind(&SetAsDefaultBrowserDialogImpl:: 337 AttemptImmersiveFirstRunRestartOnFileThread)); 338 } else { 339 // If the user explicitly elected *not to* make Chrome default, we won't 340 // ask again. 341 if (dialog_interaction_result_ == MAKE_CHROME_DEFAULT_REGRETTED) { 342 PrefService* prefs = profile_->GetPrefs(); 343 prefs->SetBoolean(prefs::kCheckDefaultBrowser, false); 344 } 345 346 // Carry on with a normal chrome session. For the purpose of surfacing this 347 // dialog the actual browser window had to remain hidden. Now it's time to 348 // show it. 349 if (browser_) { 350 BrowserWindow* window = browser_->window(); 351 WebContents* contents = 352 browser_->tab_strip_model()->GetActiveWebContents(); 353 window->Show(); 354 if (contents) 355 contents->GetView()->SetInitialFocus(); 356 } 357 } 358 359 delete this; 360 } 361 362 void SetAsDefaultBrowserDialogImpl::OnCloseContents(WebContents* source, 363 bool* out_close_dialog) { 364 *out_close_dialog = true; 365 } 366 367 bool SetAsDefaultBrowserDialogImpl::ShouldShowDialogTitle() const { 368 return true; 369 } 370 371 bool SetAsDefaultBrowserDialogImpl::HandleContextMenu( 372 const content::ContextMenuParams& params) { 373 return true; 374 } 375 376 void SetAsDefaultBrowserDialogImpl::SetDialogInteractionResult( 377 MakeChromeDefaultResult result) { 378 dialog_interaction_result_ = result; 379 } 380 381 void SetAsDefaultBrowserDialogImpl::OnBrowserRemoved(Browser* browser) { 382 if (browser_ == browser) { 383 browser_ = NULL; 384 BrowserList::RemoveObserver(this); 385 } 386 } 387 388 void SetAsDefaultBrowserDialogImpl:: 389 AttemptImmersiveFirstRunRestartOnFileThread() { 390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 391 392 // If the sentinel was created for this launch, remove it before restarting 393 // in immersive mode so that the user is taken through the full first-run 394 // flow there. 395 if (first_run::IsChromeFirstRun()) 396 first_run::RemoveSentinel(); 397 398 // Do a straight-up restart rather than a mode-switch restart. 399 // delegate_execute.exe will choose an immersive launch on the basis of the 400 // same IsTouchEnabledDevice check, but will not store this as the user's 401 // choice. 402 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 403 base::Bind(&chrome::AttemptRestart)); 404 } 405 406 } // namespace 407 408 SetAsDefaultBrowserUI::SetAsDefaultBrowserUI(content::WebUI* web_ui) 409 : ui::WebDialogUI(web_ui) { 410 content::WebUIDataSource::Add( 411 Profile::FromWebUI(web_ui), CreateSetAsDefaultBrowserUIHTMLSource()); 412 } 413 414 // static 415 void SetAsDefaultBrowserUI::Show(Profile* profile, Browser* browser) { 416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 417 SetAsDefaultBrowserDialogImpl* dialog = 418 new SetAsDefaultBrowserDialogImpl(profile, browser); 419 dialog->ShowDialog(); 420 } 421