summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/speech/chrome_speech_recognition_manager_delegate.cc217
-rw-r--r--chrome/browser/speech/chrome_speech_recognition_manager_delegate.h9
-rw-r--r--chrome/browser/speech/speech_recognition_bubble_controller.cc253
-rw-r--r--chrome/browser/speech/speech_recognition_bubble_controller.h122
-rw-r--r--chrome/browser/speech/speech_recognition_bubble_controller_unittest.cc13
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