diff options
author | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-28 21:48:58 +0000 |
---|---|---|
committer | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-28 21:48:58 +0000 |
commit | a33339963667e05430c3e67a8a35d499b432710b (patch) | |
tree | f33e22b2c3a82c24eb6ee5b8571b88d96db81553 | |
parent | 6b26b96010bb329642eeb56c2da56e9340cbe847 (diff) | |
download | chromium_src-a33339963667e05430c3e67a8a35d499b432710b.zip chromium_src-a33339963667e05430c3e67a8a35d499b432710b.tar.gz chromium_src-a33339963667e05430c3e67a8a35d499b432710b.tar.bz2 |
Add code to prompt for browser login during app notification setup
BUG=98145
TEST=Start not signed in to sync, and install an app with the 'experimental'
permission. Then visit a page in the app which calls
chrome.app.experimental.getNotificationChannel({clientId:"foo"}) - you should
get an infobar asking you to sign in.
Review URL: http://codereview.chromium.org/8400027
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107800 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/generated_resources.grd | 11 | ||||
-rw-r--r-- | chrome/browser/extensions/app_notify_channel_setup.cc | 66 | ||||
-rw-r--r-- | chrome/browser/extensions/app_notify_channel_setup.h | 14 | ||||
-rw-r--r-- | chrome/browser/extensions/app_notify_channel_setup_unittest.cc | 51 | ||||
-rw-r--r-- | chrome/browser/extensions/app_notify_channel_ui.cc | 145 | ||||
-rw-r--r-- | chrome/browser/extensions/app_notify_channel_ui.h | 79 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_tab_helper.cc | 4 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 |
8 files changed, 342 insertions, 30 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 111cbbf..006e21f 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -8939,6 +8939,17 @@ Keep your key file in a safe place. You will need it to create new versions of y </message> </if> + <!-- App Notifications --> + <message name="IDS_APP_NOTIFICATION_NEED_SIGNIN" desc="Displayed when an app wants to send server push notifictions to Chrome, but the user isn't signed in to the browser (which is required for the feature to work)."> + <ph name="APP_NAME">$1<ex>Google News</ex></ph> would like to send notifications, but you need to be signed in to Chrome. + </message> + <message name="IDS_APP_NOTIFICATION_NEED_SIGNIN_ACCEPT" desc="Text shown for the button that will take you to sign in to the browser."> + Sign in now + </message> + <message name="IDS_APP_NOTIFICATION_NEED_SIGNIN_CANCEL" desc="Text shown for the button to indicate the user is not interested in the feature."> + No thanks + </message> + <!-- Offline page --> <if expr="pp_ifdef('chromeos')"> <message name="IDS_OFFLINE_LOAD_HEADLINE" desc="Offline Page HTML headline"> diff --git a/chrome/browser/extensions/app_notify_channel_setup.cc b/chrome/browser/extensions/app_notify_channel_setup.cc index f548ce9..e990e1a1 100644 --- a/chrome/browser/extensions/app_notify_channel_setup.cc +++ b/chrome/browser/extensions/app_notify_channel_setup.cc @@ -4,6 +4,7 @@ #include "chrome/browser/extensions/app_notify_channel_setup.h" +#include "base/bind.h" #include "base/command_line.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" @@ -20,13 +21,15 @@ AppNotifyChannelSetup::AppNotifyChannelSetup( const GURL& requestor_url, int return_route_id, int callback_id, + AppNotifyChannelUI* ui, base::WeakPtr<AppNotifyChannelSetup::Delegate> delegate) : profile_(profile), client_id_(client_id), requestor_url_(requestor_url), return_route_id_(return_route_id), callback_id_(callback_id), - delegate_(delegate) {} + delegate_(delegate), + ui_(ui) {} AppNotifyChannelSetup::~AppNotifyChannelSetup() {} @@ -52,24 +55,51 @@ static GURL GetChannelServerURL() { void AppNotifyChannelSetup::Start() { AddRef(); // Balanced in ReportResult. - GURL channel_server_url = GetChannelServerURL(); - // Check if the user is logged in to the browser. std::string username = profile_->GetPrefs()->GetString( prefs::kGoogleServicesUsername); - // TODO(asargent) - If the user is not logged in, we'd like to prompt for - // login and if then they sign in, continue as normal. But for now just return - // an error. We do this via PostTask instead of immediately calling back the + if (username.empty()) { + ui_->PromptSyncSetup(this); + return; // We'll get called back in OnSyncSetupResult + } + + BeginFetch(); +} + +void AppNotifyChannelSetup::OnURLFetchComplete( + const content::URLFetcher* source) { + CHECK(source); + net::URLRequestStatus status = source->GetStatus(); + + if (status.status() == net::URLRequestStatus::SUCCESS && + source->GetResponseCode() == 200) { + // TODO(asargent) - we need to parse the response from |source| here. + ReportResult("dummy_do_not_use", ""); + } else { + ReportResult("", "channel_service_error"); + } +} + +void AppNotifyChannelSetup::OnSyncSetupResult(bool enabled) { + if (enabled) { + BeginFetch(); + } else { + ReportResult("", "not_available"); + } +} + +void AppNotifyChannelSetup::BeginFetch() { + GURL channel_server_url = GetChannelServerURL(); + + // We return the error via PostTask instead of immediately calling back the // delegate because it simplifies tests. - if (!channel_server_url.is_valid() || username.empty()) { + if (!channel_server_url.is_valid()) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - NewRunnableMethod(this, - &AppNotifyChannelSetup::ReportResult, - std::string(), - std::string("not_available"))); + base::Bind(&AppNotifyChannelSetup::ReportResult, this, + std::string(), std::string("not_available"))); return; } @@ -85,20 +115,6 @@ void AppNotifyChannelSetup::Start() { url_fetcher_->Start(); } -void AppNotifyChannelSetup::OnURLFetchComplete( - const content::URLFetcher* source) { - CHECK(source); - net::URLRequestStatus status = source->GetStatus(); - - if (status.status() == net::URLRequestStatus::SUCCESS && - source->GetResponseCode() == 200) { - // TODO(asargent) - we need to parse the response from |source| here. - ReportResult("dummy_do_not_use", ""); - } else { - ReportResult("", "channel_service_error"); - } -} - void AppNotifyChannelSetup::ReportResult( const std::string& channel_id, const std::string& error) { diff --git a/chrome/browser/extensions/app_notify_channel_setup.h b/chrome/browser/extensions/app_notify_channel_setup.h index 9a27a41..7c56275 100644 --- a/chrome/browser/extensions/app_notify_channel_setup.h +++ b/chrome/browser/extensions/app_notify_channel_setup.h @@ -8,6 +8,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "chrome/browser/extensions/app_notify_channel_ui.h" #include "content/public/common/url_fetcher_delegate.h" #include "googleurl/src/gurl.h" @@ -17,6 +18,7 @@ class Profile; // app to use when sending server push notifications. class AppNotifyChannelSetup : public content::URLFetcherDelegate, + public AppNotifyChannelUI::Delegate, public base::RefCountedThreadSafe<AppNotifyChannelSetup> { public: class Delegate { @@ -30,27 +32,32 @@ class AppNotifyChannelSetup int callback_id) = 0; }; + // Ownership of |ui| is transferred to this object. AppNotifyChannelSetup(Profile* profile, const std::string& client_id, const GURL& requestor_url, int return_route_id, int callback_id, + AppNotifyChannelUI* ui, base::WeakPtr<Delegate> delegate); // This begins the process of fetching the channel id using the browser login - // credentials. If the user isn't logged in to chrome, this will first cause a - // prompt to appear asking the user to log in. + // credentials (or using |ui_| to prompt for login if needed). void Start(); protected: // content::URLFetcherDelegate. virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE; + // AppNotifyChannelUI::Delegate. + virtual void OnSyncSetupResult(bool enabled) OVERRIDE; + private: friend class base::RefCountedThreadSafe<AppNotifyChannelSetup>; - virtual ~AppNotifyChannelSetup(); + void BeginFetch(); + void ReportResult(const std::string& channel_id, const std::string& error); Profile* profile_; @@ -60,6 +67,7 @@ class AppNotifyChannelSetup int callback_id_; base::WeakPtr<Delegate> delegate_; scoped_ptr<content::URLFetcher> url_fetcher_; + scoped_ptr<AppNotifyChannelUI> ui_; DISALLOW_COPY_AND_ASSIGN(AppNotifyChannelSetup); }; diff --git a/chrome/browser/extensions/app_notify_channel_setup_unittest.cc b/chrome/browser/extensions/app_notify_channel_setup_unittest.cc index 6b12ec3..42c8ac0 100644 --- a/chrome/browser/extensions/app_notify_channel_setup_unittest.cc +++ b/chrome/browser/extensions/app_notify_channel_setup_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/memory/weak_ptr.h" @@ -64,11 +65,53 @@ class TestDelegate : public AppNotifyChannelSetup::Delegate, DISALLOW_COPY_AND_ASSIGN(TestDelegate); }; +class TestUI : public AppNotifyChannelUI { + public: + TestUI() : delegate_(NULL) {} + ~TestUI() {} + + // AppNotifyChannelUI. + virtual void PromptSyncSetup(Delegate* delegate) OVERRIDE { + CHECK(!delegate_); + delegate_ = delegate; + + // If we have a result, post a task to call back the delegate with + // it. Otherwise ReportResult can be called manually at some later point. + if (setup_result_.get()) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&TestUI::ReportResult, + base::Unretained(this), + *setup_result_)); + } + } + + // This will make us automatically call back the delegate with |result| after + // PromptSyncSetup is called. + void SetSyncSetupResult(bool result) { + setup_result_.reset(new bool); + *setup_result_ = result; + } + + void ReportResult(bool result) { + CHECK(delegate_); + delegate_->OnSyncSetupResult(result); + } + + private: + AppNotifyChannelUI::Delegate* delegate_; + scoped_ptr<bool> setup_result_; + + DISALLOW_COPY_AND_ASSIGN(TestUI); +}; + } // namespace class AppNotifyChannelSetupTest : public testing::Test { public: - AppNotifyChannelSetupTest() : ui_thread_(BrowserThread::UI, &message_loop_) {} + AppNotifyChannelSetupTest() : ui_thread_(BrowserThread::UI, &message_loop_), + ui_(new TestUI()) { + } virtual ~AppNotifyChannelSetupTest() {} @@ -103,6 +146,7 @@ class AppNotifyChannelSetupTest : public testing::Test { page_url, kRouteId, kCallbackId, + ui_.release(), delegate_.AsWeakPtr()); setup->Start(); message_loop_.Run(); @@ -114,17 +158,20 @@ class AppNotifyChannelSetupTest : public testing::Test { content::TestBrowserThread ui_thread_; TestingProfile profile_; TestDelegate delegate_; + scoped_ptr<TestUI> ui_; }; -TEST_F(AppNotifyChannelSetupTest, NotAvailable) { +TEST_F(AppNotifyChannelSetupTest, DidNotLogInToSync) { GURL url("http://www.google.com"); + ui_->SetSyncSetupResult(false); scoped_refptr<AppNotifyChannelSetup > setup = new AppNotifyChannelSetup(&profile_, "1234", url, kRouteId, kCallbackId, + ui_.release(), delegate_.AsWeakPtr()); setup->Start(); message_loop_.Run(); diff --git a/chrome/browser/extensions/app_notify_channel_ui.cc b/chrome/browser/extensions/app_notify_channel_ui.cc new file mode 100644 index 0000000..321013f --- /dev/null +++ b/chrome/browser/extensions/app_notify_channel_ui.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/app_notify_channel_ui.h" + +#include "base/utf_string_conversions.h" +#include "chrome/browser/infobars/infobar_tab_helper.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_setup_wizard.h" +#include "chrome/browser/tab_contents/confirm_infobar_delegate.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +class AppNotifyChannelUIImpl::InfoBar : public ConfirmInfoBarDelegate { + public: + InfoBar(AppNotifyChannelUIImpl* creator, + InfoBarTabHelper* helper, + const std::string& app_name); + virtual ~InfoBar(); + + // ConfirmInfoBarDelegate. + virtual string16 GetMessageText() const OVERRIDE; + virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE; + virtual bool Accept() OVERRIDE; + virtual bool Cancel() OVERRIDE; + virtual void InfoBarDismissed() OVERRIDE; + + private: + AppNotifyChannelUIImpl* creator_; + std::string app_name_; + + DISALLOW_COPY_AND_ASSIGN(InfoBar); +}; + +AppNotifyChannelUIImpl::InfoBar::InfoBar( + AppNotifyChannelUIImpl* creator, + InfoBarTabHelper* helper, + const std::string& app_name) + : ConfirmInfoBarDelegate(helper), creator_(creator), app_name_(app_name) { +} + +AppNotifyChannelUIImpl::InfoBar::~InfoBar() {} + +string16 AppNotifyChannelUIImpl::InfoBar::GetMessageText() const { + return l10n_util::GetStringFUTF16(IDS_APP_NOTIFICATION_NEED_SIGNIN, + UTF8ToUTF16(app_name_)); +} + +string16 AppNotifyChannelUIImpl::InfoBar::GetButtonLabel( + InfoBarButton button) const { + if (button == BUTTON_OK) { + return l10n_util::GetStringUTF16(IDS_APP_NOTIFICATION_NEED_SIGNIN_ACCEPT); + } else if (button == BUTTON_CANCEL) { + return l10n_util::GetStringUTF16(IDS_APP_NOTIFICATION_NEED_SIGNIN_CANCEL); + } else { + NOTREACHED(); + } + return string16(); +} + +bool AppNotifyChannelUIImpl::InfoBar::Accept() { + creator_->OnInfoBarResult(true); + return true; +} + +bool AppNotifyChannelUIImpl::InfoBar::Cancel() { + creator_->OnInfoBarResult(false); + return true; +} + +void AppNotifyChannelUIImpl::InfoBar::InfoBarDismissed() { + Cancel(); +} + + +AppNotifyChannelUIImpl::AppNotifyChannelUIImpl(Browser* browser, + TabContentsWrapper* wrapper, + const std::string& app_name) + : browser_(browser), wrapper_(wrapper), app_name_(app_name), + delegate_(NULL), observing_sync_(false), got_first_sync_callback_(false) { +} + +AppNotifyChannelUIImpl::~AppNotifyChannelUIImpl() { + // We should have either not started observing sync, or already called + // StopObservingSync by this point. + CHECK(!observing_sync_); +} + +void AppNotifyChannelUIImpl::PromptSyncSetup( + AppNotifyChannelUI::Delegate* delegate) { + CHECK(delegate_ == NULL); + delegate_ = delegate; + + if (!browser_->profile()->HasProfileSyncService()) { + delegate_->OnSyncSetupResult(false); + return; + } + + InfoBarTabHelper* helper = wrapper_->infobar_tab_helper(); + helper->AddInfoBar(new AppNotifyChannelUIImpl::InfoBar( + this, helper, app_name_)); +} + +void AppNotifyChannelUIImpl::OnInfoBarResult(bool accepted) { + if (accepted) { + StartObservingSync(); + browser_->ShowSyncSetup(); + } else { + delegate_->OnSyncSetupResult(false); + } +} + +void AppNotifyChannelUIImpl::OnStateChanged() { + ProfileSyncService* sync_service = + browser_->profile()->GetProfileSyncService(); + bool finished = got_first_sync_callback_ && !sync_service->SetupInProgress(); + got_first_sync_callback_ = true; + + if (finished) { + StopObservingSync(); + delegate_->OnSyncSetupResult(sync_service->HasSyncSetupCompleted()); + } +} + +void AppNotifyChannelUIImpl::StartObservingSync() { + CHECK(!observing_sync_); + observing_sync_ = true; + browser_->profile()->GetProfileSyncService()->AddObserver(this); +} + +void AppNotifyChannelUIImpl::StopObservingSync() { + CHECK(observing_sync_); + observing_sync_ = false; + browser_->profile()->GetProfileSyncService()->RemoveObserver(this); +} diff --git a/chrome/browser/extensions/app_notify_channel_ui.h b/chrome/browser/extensions/app_notify_channel_ui.h new file mode 100644 index 0000000..9bdb854 --- /dev/null +++ b/chrome/browser/extensions/app_notify_channel_ui.h @@ -0,0 +1,79 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_APP_NOTIFY_CHANNEL_UI_H_ +#define CHROME_BROWSER_EXTENSIONS_APP_NOTIFY_CHANNEL_UI_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/sync/profile_sync_service_observer.h" + +class Browser; +class TabContentsWrapper; + +// An interface for prompting a user to sign in to sync so that we can create +// an app notification channel for one of their apps. +class AppNotifyChannelUI { + public: + virtual ~AppNotifyChannelUI() {} + + class Delegate { + public: + // A callback for whether the user successfully set up sync or not. + virtual void OnSyncSetupResult(bool enabled) = 0; + }; + + // Shows a prompt for sync setup - |delegate| will be called back later when + // setup is complete or cancelled. This should only be called once per + // instance. + virtual void PromptSyncSetup(Delegate* delegate) = 0; +}; + + +class AppNotifyChannelUIImpl : public AppNotifyChannelUI, + public ProfileSyncServiceObserver { + public: + AppNotifyChannelUIImpl(Browser* browser, + TabContentsWrapper* wrapper, + const std::string& app_name); + virtual ~AppNotifyChannelUIImpl(); + + // AppNotifyChannelUI. + virtual void PromptSyncSetup(AppNotifyChannelUI::Delegate* delegate) OVERRIDE; + + protected: + // A private class we use to put up an infobar - its lifetime is managed by + // |wrapper_|, so we don't have one as an instance variable. + class InfoBar; + friend class AppNotifyChannelUIImpl::InfoBar; + + // Called by our InfoBar when it's accepted or cancelled/closed. + void OnInfoBarResult(bool accepted); + + // ProfileSyncServiceObserver. + virtual void OnStateChanged() OVERRIDE; + + private: + void StartObservingSync(); + void StopObservingSync(); + + Browser* browser_; + TabContentsWrapper* wrapper_; + std::string app_name_; + AppNotifyChannelUI::Delegate* delegate_; + + // Have we registered ourself as a ProfileSyncServiceObserver? + bool observing_sync_; + + // This is for working around a bug where the first ProfileSyncServiceObserver + // callback after starting the sync login process erroneously reports + // SetupInProgress as false. See crbug.com/101842. + bool got_first_sync_callback_; + + DISALLOW_COPY_AND_ASSIGN(AppNotifyChannelUIImpl); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_APP_NOTIFY_CHANNEL_UI_H_ diff --git a/chrome/browser/extensions/extension_tab_helper.cc b/chrome/browser/extensions/extension_tab_helper.cc index 46a6522..a5b726d 100644 --- a/chrome/browser/extensions/extension_tab_helper.cc +++ b/chrome/browser/extensions/extension_tab_helper.cc @@ -187,12 +187,16 @@ void ExtensionTabHelper::OnGetAppNotifyChannel( return; } + AppNotifyChannelUI* ui = new AppNotifyChannelUIImpl( + GetBrowser(), tab_contents_wrapper(), extension->name()); + scoped_refptr<AppNotifyChannelSetup> channel_setup( new AppNotifyChannelSetup(profile, client_id, requestor_url, return_route_id, callback_id, + ui, this->AsWeakPtr())); channel_setup->Start(); // We'll get called back in AppNotifyChannelSetupComplete. diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 5decbea..6d53b5b 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -946,6 +946,8 @@ 'browser/extensions/app_notification.h', 'browser/extensions/app_notification_manager.cc', 'browser/extensions/app_notification_manager.h', + 'browser/extensions/app_notify_channel_ui.cc', + 'browser/extensions/app_notify_channel_ui.h', 'browser/extensions/app_notify_channel_setup.cc', 'browser/extensions/app_notify_channel_setup.h', 'browser/extensions/app_notification_storage.cc', |