diff options
author | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-06 12:10:34 +0000 |
---|---|---|
committer | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-06 12:10:34 +0000 |
commit | 07c45ddfad280a47b4e450edfd89ab2ba233932f (patch) | |
tree | 1f33d38dd7de1e9c54c7fc12fd585db5b4d12e55 /chrome/browser | |
parent | e8dbb2d8c69118518f7d02cfeea004b538fbbbbc (diff) | |
download | chromium_src-07c45ddfad280a47b4e450edfd89ab2ba233932f.zip chromium_src-07c45ddfad280a47b4e450edfd89ab2ba233932f.tar.gz chromium_src-07c45ddfad280a47b4e450edfd89ab2ba233932f.tar.bz2 |
Changing tab closure handling logic in speech recognition code and cleaning bubble controller. (Speech CL2.6)
- Introduced TabWatcher facility class to handle de/registration for tab closure events.
- Tab closure is now handled directly in the ChromeSpeechRecognitionManagerDelegate, aborting all sessions for the corresponding renderer.
- Cleaned-up SpeechRecognitionBubbleController code: removed support for multiple bubbles (not used anymore) and tab events registration.
BUG=116954,132627
TEST=none
Review URL: https://chromiumcodereview.appspot.com/10663018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@145586 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
5 files changed, 333 insertions, 281 deletions
diff --git a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc index f22f955..24fe3e9 100644 --- a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc +++ b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc @@ -4,6 +4,7 @@ #include "chrome/browser/speech/chrome_speech_recognition_manager_delegate.h" +#include <set> #include <string> #include "base/bind.h" @@ -20,6 +21,9 @@ #include "chrome/browser/view_type_utils.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/resource_context.h" @@ -39,12 +43,9 @@ using content::BrowserThread; using content::SpeechRecognitionManager; -using content::SpeechRecognitionSessionContext; using content::WebContents; namespace { -const int kNoActiveBubble = - content::SpeechRecognitionManager::kSessionIDInvalid; const char kExtensionPrefix[] = "chrome-extension://"; @@ -123,54 +124,154 @@ class ChromeSpeechRecognitionManagerDelegate::OptionalRequestInfo DISALLOW_COPY_AND_ASSIGN(OptionalRequestInfo); }; -ChromeSpeechRecognitionManagerDelegate::ChromeSpeechRecognitionManagerDelegate() - : active_bubble_session_id_(kNoActiveBubble) { +// Simple utility to get notified when a WebContent (a tab or an extension's +// background page) is closed or crashes. Both the callback site and the +// callback thread are passed by the caller in the constructor. +// There is no restriction on the constructor, however this class must be +// destroyed on the UI thread, due to the NotificationRegistrar dependency. +class ChromeSpeechRecognitionManagerDelegate::TabWatcher + : public base::RefCountedThreadSafe<TabWatcher>, + public content::NotificationObserver { + public: + typedef base::Callback<void(int render_process_id, int render_view_id)> + TabClosedCallback; + + TabWatcher(TabClosedCallback tab_closed_callback, + BrowserThread::ID callback_thread) + : tab_closed_callback_(tab_closed_callback), + callback_thread_(callback_thread) { + } + + // Starts monitoring the WebContents corresponding to the given + // |render_process_id|, |render_view_id| pair, invoking |tab_closed_callback_| + // if closed/unloaded. + void Watch(int render_process_id, int render_view_id) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( + &TabWatcher::Watch, this, render_process_id, render_view_id)); + return; + } + + WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id, + render_view_id); + // Sessions initiated by speech input extension APIs will end up in a NULL + // WebContent here, but they are properly managed by the + // chrome::SpeechInputExtensionManager. However, sessions initiated within a + // extension using the (new) speech JS APIs, will be properly handled here. + // TODO(primiano) turn this line into a DCHECK once speech input extension + // API is deprecated. + if (!web_contents) + return; + + // Avoid multiple registrations on |registrar_| for the same |web_contents|. + if (registered_web_contents_.find(web_contents) != + registered_web_contents_.end()) { + return; + } + registered_web_contents_.insert(web_contents); + + // Lazy initialize the registrar. + if (!registrar_.get()) + registrar_.reset(new content::NotificationRegistrar()); + + registrar_->Add(this, + content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, + content::Source<WebContents>(web_contents)); + } + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, type); + + WebContents* web_contents = content::Source<WebContents>(source).ptr(); + int render_process_id = web_contents->GetRenderProcessHost()->GetID(); + int render_view_id = web_contents->GetRenderViewHost()->GetRoutingID(); + + registrar_->Remove(this, + content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, + content::Source<WebContents>(web_contents)); + registered_web_contents_.erase(web_contents); + + BrowserThread::PostTask(callback_thread_, FROM_HERE, base::Bind( + tab_closed_callback_, render_process_id, render_view_id)); + } + + private: + friend class base::RefCountedThreadSafe<TabWatcher>; + + virtual ~TabWatcher() { + // Must be destroyed on the UI thread due to |registrar_| non thread-safety. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + } + + // Lazy-initialized and used on the UI thread to handle web contents + // notifications (tab closing). + scoped_ptr<content::NotificationRegistrar> registrar_; + + // Keeps track of which WebContent(s) have been registered, in order to avoid + // double registrations on |registrar_| + std::set<content::WebContents*> registered_web_contents_; + + // Callback used to notify, on the thread specified by |callback_thread_| the + // closure of a registered tab. + TabClosedCallback tab_closed_callback_; + content::BrowserThread::ID callback_thread_; + + DISALLOW_COPY_AND_ASSIGN(TabWatcher); +}; + +ChromeSpeechRecognitionManagerDelegate +::ChromeSpeechRecognitionManagerDelegate() { } -ChromeSpeechRecognitionManagerDelegate:: - ~ChromeSpeechRecognitionManagerDelegate() { +ChromeSpeechRecognitionManagerDelegate +::~ChromeSpeechRecognitionManagerDelegate() { if (tray_icon_controller_.get()) tray_icon_controller_->Hide(); - if (active_bubble_session_id_ != kNoActiveBubble) { - DCHECK(bubble_controller_.get()); - bubble_controller_->CloseBubble(active_bubble_session_id_); - } + if (bubble_controller_.get()) + bubble_controller_->CloseBubble(); } void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked( int session_id, SpeechRecognitionBubble::Button button) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(active_bubble_session_id_, session_id); // Note, the session might have been destroyed, therefore avoid calls to the // manager which imply its existance (e.g., GetSessionContext()). if (button == SpeechRecognitionBubble::BUTTON_CANCEL) { - GetBubbleController()->CloseBubble(session_id); + GetBubbleController()->CloseBubble(); last_session_config_.reset(); - active_bubble_session_id_ = kNoActiveBubble; // We can safely call AbortSession even if the session has already ended, // the manager's public methods are reliable and will handle it properly. SpeechRecognitionManager::GetInstance()->AbortSession(session_id); } else if (button == SpeechRecognitionBubble::BUTTON_TRY_AGAIN) { - GetBubbleController()->CloseBubble(session_id); - active_bubble_session_id_ = kNoActiveBubble; + GetBubbleController()->CloseBubble(); RestartLastSession(); + } else { + NOTREACHED(); } } void ChromeSpeechRecognitionManagerDelegate::InfoBubbleFocusChanged( int session_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK_EQ(active_bubble_session_id_, session_id); + + // This check is needed since on some systems (MacOS), in rare cases, if the + // user clicks repeatedly and fast on the input element, the FocusChanged + // event (corresponding to the old session that should be aborted) can be + // received after a new session (corresponding to the 2nd click) is started. + if (GetBubbleController()->GetActiveSessionID() != session_id) + return; // Note, the session might have been destroyed, therefore avoid calls to the // manager which imply its existance (e.g., GetSessionContext()). - - GetBubbleController()->CloseBubble(session_id); + GetBubbleController()->CloseBubble(); last_session_config_.reset(); - active_bubble_session_id_ = kNoActiveBubble; // If the user clicks outside the bubble while capturing audio we abort the // session. Otherwise, i.e. audio capture is ended and we are just waiting for @@ -183,11 +284,31 @@ void ChromeSpeechRecognitionManagerDelegate::RestartLastSession() { DCHECK(last_session_config_.get()); SpeechRecognitionManager* manager = SpeechRecognitionManager::GetInstance(); const int new_session_id = manager->CreateSession(*last_session_config_); - DCHECK_NE(new_session_id, kNoActiveBubble); + DCHECK_NE(SpeechRecognitionManager::kSessionIDInvalid, new_session_id); last_session_config_.reset(); manager->StartSession(new_session_id); } +void ChromeSpeechRecognitionManagerDelegate::TabClosedCallback( + int render_process_id, int render_view_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + SpeechRecognitionManager* manager = SpeechRecognitionManager::GetInstance(); + // |manager| becomes NULL if a browser shutdown happens between the post of + // this task (from the UI thread) and this call (on the IO thread). In this + // case we just return. + if (!manager) + return; + + manager->AbortAllSessionsForRenderView(render_process_id, render_view_id); + + if (bubble_controller_.get() && + bubble_controller_->IsShowingBubbleForRenderView(render_process_id, + render_view_id)) { + bubble_controller_->CloseBubble(); + } +} + void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart( int session_id) { const content::SpeechRecognitionSessionContext& context = @@ -199,21 +320,27 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart( SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id))); // Create and show the bubble. - DCHECK_EQ(active_bubble_session_id_, kNoActiveBubble); - active_bubble_session_id_ = session_id; GetBubbleController()->CreateBubble(session_id, context.render_process_id, context.render_view_id, context.element_rect); + } - // TODO(primiano) Why not creating directly the bubble in warmup mode? - GetBubbleController()->SetBubbleWarmUpMode(session_id); + // Register callback to auto abort session on tab closure. + // |tab_watcher_| is lazyly istantiated on the first call. + if (!tab_watcher_.get()) { + tab_watcher_ = new TabWatcher( + base::Bind(&ChromeSpeechRecognitionManagerDelegate::TabClosedCallback, + base::Unretained(this)), + BrowserThread::IO); } + tab_watcher_->Watch(context.render_process_id, context.render_view_id); } void ChromeSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) { if (RequiresBubble(session_id)) { - GetBubbleController()->SetBubbleRecordingMode(session_id); + DCHECK_EQ(session_id, GetBubbleController()->GetActiveSessionID()); + GetBubbleController()->SetBubbleRecordingMode(); } else if (RequiresTrayIcon(session_id)) { // We post the action to the UI thread for sessions requiring a tray icon, // since ChromeSpeechRecognitionPreferences (which requires UI thread) is @@ -242,8 +369,9 @@ void ChromeSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) { void ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) { // OnAudioEnd can be also raised after an abort, when the bubble has already // been closed. - if (RequiresBubble(session_id) && active_bubble_session_id_ == session_id) { - GetBubbleController()->SetBubbleRecognizingMode(session_id); + if (GetBubbleController()->GetActiveSessionID() == session_id) { + DCHECK(RequiresBubble(session_id)); + GetBubbleController()->SetBubbleRecognizingMode(); } else if (RequiresTrayIcon(session_id)) { GetTrayIconController()->Hide(); } @@ -251,19 +379,13 @@ void ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) { void ChromeSpeechRecognitionManagerDelegate::OnRecognitionResult( int session_id, const content::SpeechRecognitionResult& result) { - // A result can be dispatched when the bubble is not visible anymore (e.g., - // lost focus while waiting for a result, thus continuing in background). - if (RequiresBubble(session_id) && active_bubble_session_id_ == session_id) { - GetBubbleController()->CloseBubble(session_id); - last_session_config_.reset(); - active_bubble_session_id_ = kNoActiveBubble; - } + // The bubble will be closed upon the OnEnd event, which will follow soon. } void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError( int session_id, const content::SpeechRecognitionError& error) { // An error can be dispatched when the bubble is not visible anymore. - if (active_bubble_session_id_ != session_id) + if (GetBubbleController()->GetActiveSessionID() != session_id) return; DCHECK(RequiresBubble(session_id)); @@ -299,28 +421,29 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError( return; } GetBubbleController()->SetBubbleMessage( - session_id, l10n_util::GetStringUTF16(error_message_id)); + l10n_util::GetStringUTF16(error_message_id)); } void ChromeSpeechRecognitionManagerDelegate::OnAudioLevelsChange( int session_id, float volume, float noise_volume) { - if (active_bubble_session_id_ == session_id) { + if (GetBubbleController()->GetActiveSessionID() == session_id) { DCHECK(RequiresBubble(session_id)); - GetBubbleController()->SetBubbleInputVolume(session_id, - volume, noise_volume); + GetBubbleController()->SetBubbleInputVolume(volume, noise_volume); } else if (RequiresTrayIcon(session_id)) { GetTrayIconController()->SetVUMeterVolume(volume); } } void ChromeSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) { - // No need to remove the bubble here, since either one of the following events - // must have happened prior to this callback: - // - A previous OnRecognitionResult event already closed the bubble. - // - An error occurred, so the bubble is showing the error and will be closed - // when it will lose focus (by InfoBubbleFocusChanged()). - // - The bubble lost focus or the user pressed the Cancel button, thus it has - // been closed by InfoBubbleFocusChanged(), which triggered an AbortSession. + // The only case in which the OnRecognitionEnd should not close the bubble is + // when we are showing an error. In this case the bubble will be closed by + // the |InfoBubbleFocusChanged| method, when the users clicks either the + // "Cancel" button or outside of the bubble. + if (GetBubbleController()->GetActiveSessionID() == session_id && + !GetBubbleController()->IsShowingMessage()) { + DCHECK(RequiresBubble(session_id)); + GetBubbleController()->CloseBubble(); + } } void ChromeSpeechRecognitionManagerDelegate::GetDiagnosticInformation( diff --git a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h index eb1f4d5..519b152 100644 --- a/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h +++ b/chrome/browser/speech/chrome_speech_recognition_manager_delegate.h @@ -16,7 +16,6 @@ class SpeechRecognitionTrayIconController; namespace speech { - // This is Chrome's implementation of the SpeechRecognitionManagerDelegate // interface. class ChromeSpeechRecognitionManagerDelegate @@ -58,6 +57,7 @@ class ChromeSpeechRecognitionManagerDelegate private: class OptionalRequestInfo; + class TabWatcher; // Shows the recognition tray icon for a given |context_name|, eventually // with a notification balloon. The balloon is shown only once per profile @@ -80,6 +80,9 @@ class ChromeSpeechRecognitionManagerDelegate // (which is copied into |last_session_config_|). Used for "try again". void RestartLastSession(); + // Callback called by |tab_watcher_| on the IO thread to signal tab closure. + void TabClosedCallback(int render_process_id, int render_view_id); + // Lazy initializers for bubble and tray icon controller. SpeechRecognitionBubbleController* GetBubbleController(); SpeechRecognitionTrayIconController* GetTrayIconController(); @@ -88,9 +91,7 @@ class ChromeSpeechRecognitionManagerDelegate scoped_refptr<SpeechRecognitionTrayIconController> tray_icon_controller_; scoped_refptr<OptionalRequestInfo> optional_request_info_; scoped_ptr<content::SpeechRecognitionSessionConfig> last_session_config_; - - // TODO(primiano) this information should be kept into the bubble_controller_. - int active_bubble_session_id_; + scoped_refptr<TabWatcher> tab_watcher_; DISALLOW_COPY_AND_ASSIGN(ChromeSpeechRecognitionManagerDelegate); }; diff --git a/chrome/browser/speech/speech_recognition_bubble_controller.cc b/chrome/browser/speech/speech_recognition_bubble_controller.cc index c03d019..930b5cb 100644 --- a/chrome/browser/speech/speech_recognition_bubble_controller.cc +++ b/chrome/browser/speech/speech_recognition_bubble_controller.cc @@ -10,19 +10,30 @@ #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" -#include "ui/gfx/rect.h" using content::BrowserThread; using content::WebContents; +namespace { +const int kInvalidSessionId = 0; +} + namespace speech { SpeechRecognitionBubbleController::SpeechRecognitionBubbleController( Delegate* delegate) : delegate_(delegate), - current_bubble_session_id_(0), - registrar_(new content::NotificationRegistrar) { + current_bubble_session_id_(kInvalidSessionId), + current_bubble_render_process_id_(0), + current_bubble_render_view_id_(0), + last_request_issued_(REQUEST_CLOSE) { +} + +SpeechRecognitionBubbleController::~SpeechRecognitionBubbleController() { + DCHECK_EQ(kInvalidSessionId, current_bubble_session_id_); } void SpeechRecognitionBubbleController::CreateBubble( @@ -30,216 +41,158 @@ void SpeechRecognitionBubbleController::CreateBubble( int render_process_id, int render_view_id, const gfx::Rect& element_rect) { - if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&SpeechRecognitionBubbleController::CreateBubble, this, - session_id, render_process_id, render_view_id, - element_rect)); - return; - } - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id, - render_view_id); - - DCHECK_EQ(0u, bubbles_.count(session_id)); - SpeechRecognitionBubble* bubble = SpeechRecognitionBubble::Create( - web_contents, this, element_rect); - if (!bubble) { - // Could be null if tab or display rect were invalid. - // Simulate the cancel button being clicked to inform the delegate. - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind( - &SpeechRecognitionBubbleController::InvokeDelegateButtonClicked, - this, session_id, SpeechRecognitionBubble::BUTTON_CANCEL)); - return; - } - - bubbles_[session_id] = bubble; + current_bubble_session_id_ = session_id; + current_bubble_render_process_id_ = render_process_id; + current_bubble_render_view_id_ = render_view_id; - UpdateTabContentsSubscription(session_id, BUBBLE_ADDED); + UIRequest request(REQUEST_CREATE); + request.render_process_id = render_process_id; + request.render_view_id = render_view_id; + request.element_rect = element_rect; + ProcessRequestInUiThread(request); } -void SpeechRecognitionBubbleController::SetBubbleWarmUpMode(int session_id) { - ProcessRequestInUiThread(session_id, REQUEST_SET_WARM_UP_MODE, - string16(), 0, 0); +void SpeechRecognitionBubbleController::SetBubbleRecordingMode() { + ProcessRequestInUiThread(UIRequest(REQUEST_SET_RECORDING_MODE)); } -void SpeechRecognitionBubbleController::SetBubbleRecordingMode(int session_id) { - ProcessRequestInUiThread(session_id, REQUEST_SET_RECORDING_MODE, - string16(), 0, 0); +void SpeechRecognitionBubbleController::SetBubbleRecognizingMode() { + ProcessRequestInUiThread(UIRequest(REQUEST_SET_RECOGNIZING_MODE)); } -void SpeechRecognitionBubbleController::SetBubbleRecognizingMode( - int session_id) { - ProcessRequestInUiThread(session_id, REQUEST_SET_RECOGNIZING_MODE, - string16(), 0, 0); +void SpeechRecognitionBubbleController::SetBubbleMessage(const string16& text) { + UIRequest request(REQUEST_SET_MESSAGE); + request.message = text; + ProcessRequestInUiThread(request); } -void SpeechRecognitionBubbleController::SetBubbleMessage(int session_id, - const string16& text) { - ProcessRequestInUiThread(session_id, REQUEST_SET_MESSAGE, text, 0, 0); +bool SpeechRecognitionBubbleController::IsShowingMessage() const { + return last_request_issued_ == REQUEST_SET_MESSAGE; } void SpeechRecognitionBubbleController::SetBubbleInputVolume( - int session_id, float volume, float noise_volume) { - ProcessRequestInUiThread(session_id, REQUEST_SET_INPUT_VOLUME, string16(), - volume, noise_volume); + float volume, float noise_volume) { + UIRequest request(REQUEST_SET_INPUT_VOLUME); + request.volume = volume; + request.noise_volume = noise_volume; + ProcessRequestInUiThread(request); } -void SpeechRecognitionBubbleController::CloseBubble(int session_id) { - ProcessRequestInUiThread(session_id, REQUEST_CLOSE, string16(), 0, 0); +void SpeechRecognitionBubbleController::CloseBubble() { + current_bubble_session_id_ = kInvalidSessionId; + ProcessRequestInUiThread(UIRequest(REQUEST_CLOSE)); +} + +int SpeechRecognitionBubbleController::GetActiveSessionID() const { + return current_bubble_session_id_; +} + +bool SpeechRecognitionBubbleController::IsShowingBubbleForRenderView( + int render_process_id, int render_view_id) { + return (current_bubble_session_id_ != kInvalidSessionId) && + (current_bubble_render_process_id_ == render_process_id) && + (current_bubble_render_view_id_ == render_view_id); } void SpeechRecognitionBubbleController::InfoBubbleButtonClicked( SpeechRecognitionBubble::Button button) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(current_bubble_session_id_); - BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind( &SpeechRecognitionBubbleController::InvokeDelegateButtonClicked, - this, current_bubble_session_id_, button)); + this, button)); } void SpeechRecognitionBubbleController::InfoBubbleFocusChanged() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(current_bubble_session_id_); - - int old_bubble_session_id = current_bubble_session_id_; - current_bubble_session_id_ = 0; - BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind( &SpeechRecognitionBubbleController::InvokeDelegateFocusChanged, - this, old_bubble_session_id)); -} - -void SpeechRecognitionBubbleController::Observe( - int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { - // Cancel all bubbles and active recognition sessions for this tab. - WebContents* web_contents = content::Source<WebContents>(source).ptr(); - BubbleSessionIdMap::iterator iter = bubbles_.begin(); - while (iter != bubbles_.end()) { - if (iter->second->GetWebContents() == web_contents) { - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind( - &SpeechRecognitionBubbleController::InvokeDelegateButtonClicked, - this, iter->first, SpeechRecognitionBubble::BUTTON_CANCEL)); - CloseBubble(iter->first); - // We expect to have a very small number of items in this map so - // redo-ing from start is ok. - iter = bubbles_.begin(); - } else { - ++iter; - } - } - } else { - NOTREACHED() << "Unknown notification"; - } -} - -SpeechRecognitionBubbleController::~SpeechRecognitionBubbleController() { - DCHECK(bubbles_.empty()); + this)); } void SpeechRecognitionBubbleController::InvokeDelegateButtonClicked( - int session_id, SpeechRecognitionBubble::Button button) { - delegate_->InfoBubbleButtonClicked(session_id, button); + SpeechRecognitionBubble::Button button) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_NE(kInvalidSessionId, current_bubble_session_id_); + delegate_->InfoBubbleButtonClicked(current_bubble_session_id_, button); } -void SpeechRecognitionBubbleController::InvokeDelegateFocusChanged( - int session_id) { - delegate_->InfoBubbleFocusChanged(session_id); +void SpeechRecognitionBubbleController::InvokeDelegateFocusChanged() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_NE(kInvalidSessionId, current_bubble_session_id_); + delegate_->InfoBubbleFocusChanged(current_bubble_session_id_); } void SpeechRecognitionBubbleController::ProcessRequestInUiThread( - int session_id, RequestType type, const string16& text, float volume, - float noise_volume) { + const UIRequest& request) { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + last_request_issued_ = request.type; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &SpeechRecognitionBubbleController::ProcessRequestInUiThread, this, - session_id, type, text, volume, noise_volume)); + &SpeechRecognitionBubbleController::ProcessRequestInUiThread, + this, + request)); return; } - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // The bubble may have been closed before we got a chance to process this - // request. So check before proceeding. - if (!bubbles_.count(session_id)) - return; - bool change_active_bubble = (type == REQUEST_SET_WARM_UP_MODE || - type == REQUEST_SET_MESSAGE); - if (change_active_bubble) { - if (current_bubble_session_id_ && current_bubble_session_id_ != session_id) - bubbles_[current_bubble_session_id_]->Hide(); - current_bubble_session_id_ = session_id; - } + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - SpeechRecognitionBubble* bubble = bubbles_[session_id]; - switch (type) { - case REQUEST_SET_WARM_UP_MODE: - bubble->SetWarmUpMode(); + switch (request.type) { + case REQUEST_CREATE: + bubble_.reset(SpeechRecognitionBubble::Create( + tab_util::GetWebContentsByID(request.render_process_id, + request.render_view_id), + this, + request.element_rect)); + + if (!bubble_.get()) { + // Could be null if tab or display rect were invalid. + // Simulate the cancel button being clicked to inform the delegate. + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind( + &SpeechRecognitionBubbleController::InvokeDelegateButtonClicked, + this, SpeechRecognitionBubble::BUTTON_CANCEL)); + return; + } + bubble_->Show(); + bubble_->SetWarmUpMode(); break; case REQUEST_SET_RECORDING_MODE: - bubble->SetRecordingMode(); + DCHECK(bubble_.get()); + bubble_->SetRecordingMode(); break; case REQUEST_SET_RECOGNIZING_MODE: - bubble->SetRecognizingMode(); + DCHECK(bubble_.get()); + bubble_->SetRecognizingMode(); break; case REQUEST_SET_MESSAGE: - bubble->SetMessage(text); + DCHECK(bubble_.get()); + bubble_->SetMessage(request.message); break; case REQUEST_SET_INPUT_VOLUME: - bubble->SetInputVolume(volume, noise_volume); + DCHECK(bubble_.get()); + bubble_->SetInputVolume(request.volume, request.noise_volume); break; case REQUEST_CLOSE: - if (current_bubble_session_id_ == session_id) - current_bubble_session_id_ = 0; - UpdateTabContentsSubscription(session_id, BUBBLE_REMOVED); - delete bubble; - bubbles_.erase(session_id); + bubble_.reset(); break; default: NOTREACHED(); break; } - - if (change_active_bubble) - bubble->Show(); } -void SpeechRecognitionBubbleController::UpdateTabContentsSubscription( - int session_id, ManageSubscriptionAction action) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - // If there are any other bubbles existing for the same WebContents, we would - // have subscribed to tab close notifications on their behalf and we need to - // stay registered. So we don't change the subscription in such cases. - WebContents* web_contents = bubbles_[session_id]->GetWebContents(); - for (BubbleSessionIdMap::iterator iter = bubbles_.begin(); - iter != bubbles_.end(); ++iter) { - if (iter->second->GetWebContents() == web_contents && - iter->first != session_id) { - // At least one other bubble exists for the same WebContents. So don't - // make any change to the subscription. - return; - } - } +SpeechRecognitionBubbleController::UIRequest::UIRequest(RequestType type_value) + : type(type_value), + volume(0.0F), + noise_volume(0.0F), + render_process_id(0), + render_view_id(0) { +} - if (action == BUBBLE_ADDED) { - registrar_->Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, - content::Source<WebContents>(web_contents)); - } else { - registrar_->Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, - content::Source<WebContents>(web_contents)); - } +SpeechRecognitionBubbleController::UIRequest::~UIRequest() { } } // namespace speech diff --git a/chrome/browser/speech/speech_recognition_bubble_controller.h b/chrome/browser/speech/speech_recognition_bubble_controller.h index cdc042d..e5b18a5 100644 --- a/chrome/browser/speech/speech_recognition_bubble_controller.h +++ b/chrome/browser/speech/speech_recognition_bubble_controller.h @@ -6,35 +6,21 @@ #define CHROME_BROWSER_SPEECH_SPEECH_RECOGNITION_BUBBLE_CONTROLLER_H_ #pragma once -#include <map> - #include "base/basictypes.h" #include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" #include "chrome/browser/speech/speech_recognition_bubble.h" -#include "content/public/browser/notification_observer.h" - -namespace gfx { -class Rect; -} - -namespace content { -class NotificationRegistrar; -} +#include "ui/gfx/rect.h" namespace speech { // This class handles the speech recognition popup UI on behalf of -// SpeechRecognitionManager, which invokes methods in the IO thread, processing -// those requests in the UI thread. There could be multiple bubble objects alive -// at the same time but only one of them is visible to the user. User actions on -// that bubble are reported to the delegate. +// SpeechRecognitionManager, which invokes methods on the IO thread, processing +// those requests on the UI thread. At most one bubble can be active. class SpeechRecognitionBubbleController : public base::RefCountedThreadSafe<SpeechRecognitionBubbleController>, - public SpeechRecognitionBubbleDelegate, - public content::NotificationObserver { + public SpeechRecognitionBubbleDelegate { public: - // All methods of this delegate are called in the IO thread. + // All methods of this delegate are called on the IO thread. class Delegate { public: // Invoked when the user clicks on a button in the speech recognition UI. @@ -51,50 +37,48 @@ class SpeechRecognitionBubbleController explicit SpeechRecognitionBubbleController(Delegate* delegate); - // Creates a new speech recognition UI bubble. One of the SetXxxx methods - // below need to be called to specify what to display. + // Creates and shows a new speech recognition UI bubble in warmup mode. void CreateBubble(int session_id, int render_process_id, int render_view_id, const gfx::Rect& element_rect); - // Indicates to the user that audio hardware is warming up. This also makes - // the bubble visible if not already visible. - void SetBubbleWarmUpMode(int session_id); + // Indicates to the user that audio recording is in progress. + void SetBubbleRecordingMode(); - // Indicates to the user that audio recording is in progress. This also makes - // the bubble visible if not already visible. - void SetBubbleRecordingMode(int session_id); + // Indicates to the user that recognition is in progress. + void SetBubbleRecognizingMode(); - // Indicates to the user that recognition is in progress. If the bubble is - // hidden, |Show| must be called to make it appear on screen. - void SetBubbleRecognizingMode(int session_id); + // Displays the given string with the 'Try again' and 'Cancel' buttons. + void SetBubbleMessage(const string16& text); - // Displays the given string with the 'Try again' and 'Cancel' buttons. If the - // bubble is hidden, |Show| must be called to make it appear on screen. - void SetBubbleMessage(int session_id, const string16& text); + // Checks whether the bubble is active and is showing a message. + bool IsShowingMessage() const; // Updates the current captured audio volume displayed on screen. - void SetBubbleInputVolume(int session_id, float volume, float noise_volume); + void SetBubbleInputVolume(float volume, float noise_volume); + + void CloseBubble(); + + // Retrieves the session ID associated to the active bubble (if any). + // Returns 0 if no bubble is currently shown. + int GetActiveSessionID() const; - void CloseBubble(int session_id); + // Checks whether a bubble is being shown on the RenderView (tab/extension) + // identified by the tuple {|render_process_id|,|render_view_id|} + bool IsShowingBubbleForRenderView(int render_process_id, int render_view_id); // SpeechRecognitionBubble::Delegate methods. virtual void InfoBubbleButtonClicked( SpeechRecognitionBubble::Button button) OVERRIDE; virtual void InfoBubbleFocusChanged() OVERRIDE; - // content::NotificationObserver implementation. - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; - private: friend class base::RefCountedThreadSafe<SpeechRecognitionBubbleController>; - // The various calls received by this object and handled in the UI thread. + // The various calls received by this object and handled on the UI thread. enum RequestType { - REQUEST_SET_WARM_UP_MODE, + REQUEST_CREATE, REQUEST_SET_RECORDING_MODE, REQUEST_SET_RECOGNIZING_MODE, REQUEST_SET_MESSAGE, @@ -102,45 +86,39 @@ class SpeechRecognitionBubbleController REQUEST_CLOSE, }; - enum ManageSubscriptionAction { - BUBBLE_ADDED, - BUBBLE_REMOVED + struct UIRequest { + RequestType type; + string16 message; + gfx::Rect element_rect; + float volume; + float noise_volume; + int render_process_id; + int render_view_id; + + explicit UIRequest(RequestType type_value); + ~UIRequest(); }; virtual ~SpeechRecognitionBubbleController(); - void InvokeDelegateButtonClicked(int session_id, - SpeechRecognitionBubble::Button button); - void InvokeDelegateFocusChanged(int session_id); - void ProcessRequestInUiThread(int session_id, - RequestType type, - const string16& text, - float volume, - float noise_volume); - - // Called whenever a bubble was added to or removed from the list. If the - // bubble was being added, this method registers for close notifications with - // the WebContents if this was the first bubble for the tab. Similarly if the - // bubble was being removed, this method unregisters from WebContents if this - // was the last bubble associated with that tab. - void UpdateTabContentsSubscription(int session_id, - ManageSubscriptionAction action); - - // Only accessed in the IO thread. - Delegate* delegate_; + void InvokeDelegateButtonClicked(SpeechRecognitionBubble::Button button); + void InvokeDelegateFocusChanged(); + void ProcessRequestInUiThread(const UIRequest& request); - // *** The following are accessed only in the UI thread. + // *** The following are accessed only on the IO thread. + Delegate* delegate_; - // The session id for currently visible bubble (since only one bubble is - // visible at any time). + // The session id for currently visible bubble. int current_bubble_session_id_; - // Map of session-ids to bubble objects. The bubbles are weak pointers owned - // by this object and get destroyed by |CloseBubble|. - typedef std::map<int, SpeechRecognitionBubble*> BubbleSessionIdMap; - BubbleSessionIdMap bubbles_; + // The render process and view ids for the currently visible bubble. + int current_bubble_render_process_id_; + int current_bubble_render_view_id_; + + RequestType last_request_issued_; - scoped_ptr<content::NotificationRegistrar> registrar_; + // *** The following are accessed only on the UI thread. + scoped_ptr<SpeechRecognitionBubble> bubble_; }; // This typedef is to workaround the issue with certain versions of diff --git a/chrome/browser/speech/speech_recognition_bubble_controller_unittest.cc b/chrome/browser/speech/speech_recognition_bubble_controller_unittest.cc index 03a18fd..5ccc340 100644 --- a/chrome/browser/speech/speech_recognition_bubble_controller_unittest.cc +++ b/chrome/browser/speech/speech_recognition_bubble_controller_unittest.cc @@ -128,12 +128,9 @@ class SpeechRecognitionBubbleControllerTest } static void ActivateBubble() { - if (MockSpeechRecognitionBubble::type() == + if (MockSpeechRecognitionBubble::type() != MockSpeechRecognitionBubble::BUBBLE_TEST_FOCUS_CHANGED) { - test_fixture_->controller_->SetBubbleWarmUpMode(kBubbleSessionId); - } else { - test_fixture_->controller_->SetBubbleMessage(kBubbleSessionId, - ASCIIToUTF16("Test")); + test_fixture_->controller_->SetBubbleMessage(ASCIIToUTF16("Test")); } } @@ -193,7 +190,7 @@ TEST_F(SpeechRecognitionBubbleControllerTest, TestFocusChanged) { EXPECT_TRUE(focus_changed_); EXPECT_FALSE(cancel_clicked_); EXPECT_FALSE(try_again_clicked_); - controller_->CloseBubble(kBubbleSessionId); + controller_->CloseBubble(); } // Test that the speech bubble UI gets created in the UI thread and that the @@ -207,7 +204,7 @@ TEST_F(SpeechRecognitionBubbleControllerTest, TestRecognitionCancelled) { EXPECT_TRUE(cancel_clicked_); EXPECT_FALSE(try_again_clicked_); EXPECT_FALSE(focus_changed_); - controller_->CloseBubble(kBubbleSessionId); + controller_->CloseBubble(); } // Test that the speech bubble UI gets created in the UI thread and that the @@ -221,7 +218,7 @@ TEST_F(SpeechRecognitionBubbleControllerTest, TestTryAgainClicked) { EXPECT_FALSE(cancel_clicked_); EXPECT_TRUE(try_again_clicked_); EXPECT_FALSE(focus_changed_); - controller_->CloseBubble(kBubbleSessionId); + controller_->CloseBubble(); } } // namespace speech |