summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-28 21:48:58 +0000
committerasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-28 21:48:58 +0000
commita33339963667e05430c3e67a8a35d499b432710b (patch)
treef33e22b2c3a82c24eb6ee5b8571b88d96db81553
parent6b26b96010bb329642eeb56c2da56e9340cbe847 (diff)
downloadchromium_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.grd11
-rw-r--r--chrome/browser/extensions/app_notify_channel_setup.cc66
-rw-r--r--chrome/browser/extensions/app_notify_channel_setup.h14
-rw-r--r--chrome/browser/extensions/app_notify_channel_setup_unittest.cc51
-rw-r--r--chrome/browser/extensions/app_notify_channel_ui.cc145
-rw-r--r--chrome/browser/extensions/app_notify_channel_ui.h79
-rw-r--r--chrome/browser/extensions/extension_tab_helper.cc4
-rw-r--r--chrome/chrome_browser.gypi2
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',