// Copyright (c) 2012 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 "libcef/browser/speech_recognition_manager_delegate.h" #include #include #include "libcef/common/cef_switches.h" #include "base/bind.h" #include "base/command_line.h" #include "content/public/browser/browser_thread.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_types.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/speech_recognition_manager.h" #include "content/public/browser/speech_recognition_session_context.h" #include "content/public/browser/web_contents.h" #include "content/public/common/speech_recognition_error.h" #include "content/public/common/speech_recognition_result.h" using content::BrowserThread; using content::SpeechRecognitionManager; using content::WebContents; // Simple utility to get notified when a WebContents 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 CefSpeechRecognitionManagerDelegate::WebContentsWatcher : public base::RefCountedThreadSafe, public content::NotificationObserver { public: typedef base::Callback WebContentsClosedCallback; WebContentsWatcher(WebContentsClosedCallback web_contents_closed_callback, BrowserThread::ID callback_thread) : web_contents_closed_callback_(web_contents_closed_callback), callback_thread_(callback_thread) {} // Starts monitoring the WebContents corresponding to the given // |render_process_id|, |render_view_id| pair, invoking // |web_contents_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(&WebContentsWatcher::Watch, this, render_process_id, render_view_id)); return; } WebContents* web_contents = NULL; content::RenderViewHost* render_view_host = content::RenderViewHost::FromID(render_process_id, render_view_id); if (render_view_host) web_contents = WebContents::FromRenderViewHost(render_view_host); DCHECK(web_contents); // 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(web_contents)); } // content::NotificationObserver implementation. 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(source).ptr(); int render_process_id = web_contents->GetRenderViewHost()->GetProcess()->GetID(); int render_view_id = web_contents->GetRenderViewHost()->GetRoutingID(); registrar_->Remove(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, content::Source(web_contents)); registered_web_contents_.erase(web_contents); BrowserThread::PostTask(callback_thread_, FROM_HERE, base::Bind(web_contents_closed_callback_, render_process_id, render_view_id)); } private: friend class base::RefCountedThreadSafe; ~WebContentsWatcher() override { // 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). std::unique_ptr registrar_; // Keeps track of which WebContent(s) have been registered, in order to avoid // double registrations on |registrar_| std::set registered_web_contents_; // Callback used to notify, on the thread specified by |callback_thread_| the // closure of a registered tab. WebContentsClosedCallback web_contents_closed_callback_; content::BrowserThread::ID callback_thread_; DISALLOW_COPY_AND_ASSIGN(WebContentsWatcher); }; CefSpeechRecognitionManagerDelegate ::CefSpeechRecognitionManagerDelegate() { const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); filter_profanities_ = command_line->HasSwitch(switches::kEnableProfanityFilter); } CefSpeechRecognitionManagerDelegate ::~CefSpeechRecognitionManagerDelegate() {} void CefSpeechRecognitionManagerDelegate::WebContentsClosedCallback( 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); } void CefSpeechRecognitionManagerDelegate::OnRecognitionStart(int session_id) { const content::SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); // Register callback to auto abort session on tab closure. // |web_contents_watcher_| is lazyly istantiated on the first call. if (!web_contents_watcher_.get()) { web_contents_watcher_ = new WebContentsWatcher( base::Bind( &CefSpeechRecognitionManagerDelegate::WebContentsClosedCallback, base::Unretained(this)), BrowserThread::IO); } web_contents_watcher_->Watch(context.render_process_id, context.render_view_id); } void CefSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) {} void CefSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete( int session_id) {} void CefSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) {} void CefSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) {} void CefSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) {} void CefSpeechRecognitionManagerDelegate::OnRecognitionResults( int session_id, const content::SpeechRecognitionResults& result) {} void CefSpeechRecognitionManagerDelegate::OnRecognitionError( int session_id, const content::SpeechRecognitionError& error) {} void CefSpeechRecognitionManagerDelegate::OnAudioLevelsChange( int session_id, float volume, float noise_volume) {} void CefSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) {} void CefSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed( int session_id, base::OnceCallback callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); const content::SpeechRecognitionSessionContext& context = SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); // Make sure that initiators properly set the |render_process_id| field. DCHECK_NE(context.render_process_id, 0); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::BindOnce(std::move(callback), false, true)); } content::SpeechRecognitionEventListener* CefSpeechRecognitionManagerDelegate::GetEventListener() { return this; } bool CefSpeechRecognitionManagerDelegate::FilterProfanities( int render_process_id) { return filter_profanities_; }