| // 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 "chrome/browser/speech/chrome_speech_recognition_manager_delegate.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/metrics/chrome_metrics_service_accessor.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/speech_recognition_manager.h" |
| #include "content/public/browser/speech_recognition_session_config.h" |
| #include "content/public/browser/speech_recognition_session_context.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/speech_recognition_error.h" |
| #include "content/public/common/speech_recognition_result.h" |
| #include "extensions/features/features.h" |
| #include "net/url_request/url_request_context_getter.h" |
| |
| #if defined(OS_WIN) |
| #include "chrome/installer/util/wmi.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "extensions/browser/view_type_utils.h" |
| #endif |
| |
| using content::BrowserThread; |
| using content::SpeechRecognitionManager; |
| using content::WebContents; |
| |
| namespace speech { |
| |
| namespace { |
| |
| void TabClosedCallbackOnIOThread(int render_process_id, int render_view_id) { |
| DCHECK_CURRENTLY_ON(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); |
| } |
| |
| } // namespace |
| |
| // Simple utility to get notified when a WebContent (a tab or an extension's |
| // background page) is closed or crashes. The callback will always be called on |
| // the UI thread. |
| class ChromeSpeechRecognitionManagerDelegate::TabWatcher |
| : public base::RefCountedThreadSafe<TabWatcher> { |
| public: |
| typedef base::Callback<void(int render_process_id, int render_view_id)> |
| TabClosedCallback; |
| |
| explicit TabWatcher(TabClosedCallback tab_closed_callback) |
| : tab_closed_callback_(tab_closed_callback) { |
| } |
| |
| // 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 for the same |web_contents|. |
| if (FindWebContents(web_contents) != registered_web_contents_.end()) |
| return; |
| |
| registered_web_contents_.push_back(base::MakeUnique<WebContentsTracker>( |
| web_contents, |
| base::Bind(&TabWatcher::OnTabClosed, |
| // |this| outlives WebContentsTracker. |
| base::Unretained(this), web_contents), |
| render_process_id, render_view_id)); |
| } |
| |
| void OnTabClosed(content::WebContents* web_contents) { |
| auto iter = FindWebContents(web_contents); |
| DCHECK(iter != registered_web_contents_.end()); |
| int render_process_id = (*iter)->render_process_id(); |
| int render_view_id = (*iter)->render_view_id(); |
| registered_web_contents_.erase(iter); |
| |
| tab_closed_callback_.Run(render_process_id, render_view_id); |
| } |
| |
| private: |
| class WebContentsTracker : public content::WebContentsObserver { |
| public: |
| WebContentsTracker(content::WebContents* web_contents, |
| const base::Closure& finished_callback, |
| int render_process_id, |
| int render_view_id) |
| : content::WebContentsObserver(web_contents), |
| web_contents_(web_contents), |
| finished_callback_(finished_callback), |
| render_process_id_(render_process_id), |
| render_view_id_(render_view_id) {} |
| |
| ~WebContentsTracker() override {} |
| |
| int render_process_id() const { return render_process_id_; } |
| int render_view_id() const { return render_view_id_; } |
| const content::WebContents* GetWebContents() const { return web_contents_; } |
| |
| private: |
| // content::WebContentsObserver overrides. |
| void WebContentsDestroyed() override { |
| Observe(nullptr); |
| finished_callback_.Run(); |
| // NOTE: We are deleted now. |
| } |
| void RenderViewHostChanged(content::RenderViewHost* old_host, |
| content::RenderViewHost* new_host) override { |
| Observe(nullptr); |
| finished_callback_.Run(); |
| // NOTE: We are deleted now. |
| } |
| |
| // Raw pointer to our WebContents. |
| // |
| // Although we are a WebContentsObserver, calling |
| // WebContents::web_contents() would return NULL once we unregister |
| // ourselves in WebContentsDestroyed() or RenderViewHostChanged(). So we |
| // store a reference to perform cleanup. |
| const content::WebContents* const web_contents_; |
| const base::Closure finished_callback_; |
| const int render_process_id_; |
| const int render_view_id_; |
| }; |
| |
| friend class base::RefCountedThreadSafe<TabWatcher>; |
| |
| ~TabWatcher() { |
| } |
| |
| // Helper function to find the iterator in |registered_web_contents_| which |
| // contains |web_contents|. |
| std::vector<std::unique_ptr<WebContentsTracker>>::iterator FindWebContents( |
| content::WebContents* web_contents) { |
| for (auto i = registered_web_contents_.begin(); |
| i != registered_web_contents_.end(); ++i) { |
| if ((*i)->GetWebContents() == web_contents) |
| return i; |
| } |
| |
| return registered_web_contents_.end(); |
| } |
| |
| // Keeps track of which WebContent(s) have been registered, in order to avoid |
| // double registrations on WebContentsObserver and to pass the correct render |
| // process id and render view id to |tab_closed_callback_| after the process |
| // has gone away. |
| std::vector<std::unique_ptr<WebContentsTracker>> registered_web_contents_; |
| |
| // Callback used to notify, on the thread specified by |callback_thread_| the |
| // closure of a registered tab. |
| TabClosedCallback tab_closed_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TabWatcher); |
| }; |
| |
| ChromeSpeechRecognitionManagerDelegate |
| ::ChromeSpeechRecognitionManagerDelegate() { |
| } |
| |
| ChromeSpeechRecognitionManagerDelegate |
| ::~ChromeSpeechRecognitionManagerDelegate() { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::TabClosedCallback( |
| int render_process_id, int render_view_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Tell the S.R. Manager (which lives on the IO thread) to abort all the |
| // sessions for the given renderer view. |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind( |
| &TabClosedCallbackOnIOThread, render_process_id, render_view_id)); |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart( |
| int session_id) { |
| const content::SpeechRecognitionSessionContext& context = |
| SpeechRecognitionManager::GetInstance()->GetSessionContext(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))); |
| } |
| tab_watcher_->Watch(context.render_process_id, context.render_view_id); |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete( |
| int session_id) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnRecognitionResults( |
| int session_id, const content::SpeechRecognitionResults& result) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError( |
| int session_id, const content::SpeechRecognitionError& error) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnAudioLevelsChange( |
| int session_id, float volume, float noise_volume) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) { |
| } |
| |
| void ChromeSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed( |
| int session_id, |
| base::OnceCallback<void(bool ask_user, bool is_allowed)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const content::SpeechRecognitionSessionContext& context = |
| SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id); |
| |
| // Make sure that initiators (extensions/web pages) properly set the |
| // |render_process_id| field, which is needed later to retrieve the profile. |
| DCHECK_NE(context.render_process_id, 0); |
| |
| int render_process_id = context.render_process_id; |
| int render_view_id = context.render_view_id; |
| if (context.embedder_render_process_id) { |
| // If this is a request originated from a guest, we need to re-route the |
| // permission check through the embedder (app). |
| render_process_id = context.embedder_render_process_id; |
| render_view_id = context.embedder_render_view_id; |
| } |
| |
| // Check that the render view type is appropriate, and whether or not we |
| // need to request permission from the user. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&CheckRenderViewType, std::move(callback), |
| render_process_id, render_view_id)); |
| } |
| |
| content::SpeechRecognitionEventListener* |
| ChromeSpeechRecognitionManagerDelegate::GetEventListener() { |
| return this; |
| } |
| |
| bool ChromeSpeechRecognitionManagerDelegate::FilterProfanities( |
| int render_process_id) { |
| content::RenderProcessHost* rph = |
| content::RenderProcessHost::FromID(render_process_id); |
| if (!rph) // Guard against race conditions on RPH lifetime. |
| return true; |
| |
| return Profile::FromBrowserContext(rph->GetBrowserContext())->GetPrefs()-> |
| GetBoolean(prefs::kSpeechRecognitionFilterProfanities); |
| } |
| |
| // static. |
| void ChromeSpeechRecognitionManagerDelegate::CheckRenderViewType( |
| base::OnceCallback<void(bool ask_user, bool is_allowed)> callback, |
| int render_process_id, |
| int render_view_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| content::RenderViewHost* render_view_host = |
| content::RenderViewHost::FromID(render_process_id, render_view_id); |
| |
| bool allowed = false; |
| bool check_permission = false; |
| |
| if (!render_view_host) { |
| // This happens for extensions. Manifest should be checked for permission. |
| allowed = true; |
| check_permission = false; |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(std::move(callback), check_permission, allowed)); |
| return; |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host); |
| extensions::ViewType view_type = extensions::GetViewType(web_contents); |
| |
| if (view_type == extensions::VIEW_TYPE_TAB_CONTENTS || |
| view_type == extensions::VIEW_TYPE_APP_WINDOW || |
| view_type == extensions::VIEW_TYPE_COMPONENT || |
| view_type == extensions::VIEW_TYPE_EXTENSION_POPUP || |
| view_type == extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { |
| // If it is a tab, we can check for permission. For apps, this means |
| // manifest would be checked for permission. |
| allowed = true; |
| check_permission = true; |
| } |
| #else |
| // Otherwise this should be a regular tab contents. |
| allowed = true; |
| check_permission = true; |
| #endif |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(std::move(callback), check_permission, allowed)); |
| } |
| |
| } // namespace speech |