Home | History | Annotate | Download | only in webui
      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