// 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 "base/bind.h" #include "base/command_line.h" #include "base/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" #include "base/synchronization/waitable_event.h" #include "base/utf_string_conversions.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/speech/input_tag_speech_dispatcher_host.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/notification_types.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/common/content_switches.h" #include "content/public/common/speech_recognition_error.h" #include "content/public/common/speech_recognition_result.h" #include "content/public/common/url_constants.h" #include "content/public/test/test_utils.h" #include "content/shell/shell.h" #include "content/test/content_browser_test.h" #include "content/test/content_browser_test_utils.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" namespace content { const char kTestResult[] = "Pictures of the moon"; class FakeSpeechRecognitionManager : public SpeechRecognitionManager { public: FakeSpeechRecognitionManager() : session_id_(0), listener_(NULL), did_cancel_all_(false), should_send_fake_response_(true), recognition_started_event_(false, false) { } std::string grammar() { return grammar_; } bool did_cancel_all() { return did_cancel_all_; } void set_should_send_fake_response(bool send) { should_send_fake_response_ = send; } bool should_send_fake_response() { return should_send_fake_response_; } base::WaitableEvent& recognition_started_event() { return recognition_started_event_; } // SpeechRecognitionManager methods. virtual int CreateSession( const SpeechRecognitionSessionConfig& config) OVERRIDE { VLOG(1) << "FAKE CreateSession invoked."; EXPECT_EQ(0, session_id_); EXPECT_EQ(NULL, listener_); listener_ = config.event_listener; if (config.grammars.size() > 0) grammar_ = config.grammars[0].url; session_ctx_ = config.initial_context; session_config_ = config; session_id_ = 1; return session_id_; } virtual void StartSession(int session_id) OVERRIDE { VLOG(1) << "FAKE StartSession invoked."; EXPECT_EQ(session_id, session_id_); EXPECT_TRUE(listener_ != NULL); if (should_send_fake_response_) { // Give the fake result in a short while. MessageLoop::current()->PostTask(FROM_HERE, base::Bind( &FakeSpeechRecognitionManager::SetFakeRecognitionResult, // This class does not need to be refcounted (typically done by // PostTask) since it will outlive the test and gets released only // when the test shuts down. Disabling refcounting here saves a bit // of unnecessary code and the factory method can return a plain // pointer below as required by the real code. base::Unretained(this))); } recognition_started_event_.Signal(); } virtual void AbortSession(int session_id) OVERRIDE { VLOG(1) << "FAKE AbortSession invoked."; EXPECT_EQ(session_id_, session_id); session_id_ = 0; listener_ = NULL; } virtual void StopAudioCaptureForSession(int session_id) OVERRIDE { VLOG(1) << "StopRecording invoked."; EXPECT_EQ(session_id_, session_id); // Nothing to do here since we aren't really recording. } virtual void AbortAllSessionsForListener( SpeechRecognitionEventListener* listener) OVERRIDE { VLOG(1) << "CancelAllRequestsWithDelegate invoked."; // listener_ is set to NULL if a fake result was received (see below), so // check that listener_ matches the incoming parameter only when there is // no fake result sent. EXPECT_TRUE(should_send_fake_response_ || listener_ == listener); did_cancel_all_ = true; } virtual void AbortAllSessionsForRenderView(int render_process_id, int render_view_id) OVERRIDE { NOTREACHED(); } virtual bool HasAudioInputDevices() OVERRIDE { return true; } virtual bool IsCapturingAudio() OVERRIDE { return true; } virtual string16 GetAudioInputDeviceModel() OVERRIDE { return string16(); } virtual void ShowAudioInputSettings() OVERRIDE {} virtual int GetSession(int render_process_id, int render_view_id, int request_id) const OVERRIDE { return session_ctx_.render_process_id == render_process_id && session_ctx_.render_view_id == render_view_id && session_ctx_.request_id == request_id; } virtual const SpeechRecognitionSessionConfig& GetSessionConfig( int session_id) const OVERRIDE { EXPECT_EQ(session_id, session_id_); return session_config_; } virtual SpeechRecognitionSessionContext GetSessionContext( int session_id) const OVERRIDE { EXPECT_EQ(session_id, session_id_); return session_ctx_; } private: void SetFakeRecognitionResult() { if (session_id_) { // Do a check in case we were cancelled.. VLOG(1) << "Setting fake recognition result."; listener_->OnAudioEnd(session_id_); SpeechRecognitionResult results; results.hypotheses.push_back(SpeechRecognitionHypothesis( ASCIIToUTF16(kTestResult), 1.0)); listener_->OnRecognitionResult(session_id_, results); listener_->OnRecognitionEnd(session_id_); session_id_ = 0; listener_ = NULL; VLOG(1) << "Finished setting fake recognition result."; } } int session_id_; SpeechRecognitionEventListener* listener_; SpeechRecognitionSessionConfig session_config_; SpeechRecognitionSessionContext session_ctx_; std::string grammar_; bool did_cancel_all_; bool should_send_fake_response_; base::WaitableEvent recognition_started_event_; }; class SpeechRecognitionBrowserTest : public ContentBrowserTest { public: // ContentBrowserTest methods virtual void SetUpCommandLine(CommandLine* command_line) { EXPECT_TRUE(!command_line->HasSwitch(switches::kDisableSpeechInput)); } protected: void LoadAndStartSpeechRecognitionTest(const char* filename) { // The test page calculates the speech button's coordinate in the page on // load & sets that coordinate in the URL fragment. We send mouse down & up // events at that coordinate to trigger speech recognition. GURL test_url = GetTestUrl("speech", filename); NavigateToURL(shell(), test_url); WebKit::WebMouseEvent mouse_event; mouse_event.type = WebKit::WebInputEvent::MouseDown; mouse_event.button = WebKit::WebMouseEvent::ButtonLeft; mouse_event.x = 0; mouse_event.y = 0; mouse_event.clickCount = 1; WebContents* web_contents = shell()->web_contents(); WindowedNotificationObserver observer( NOTIFICATION_LOAD_STOP, Source(&web_contents->GetController())); web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); mouse_event.type = WebKit::WebInputEvent::MouseUp; web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); fake_speech_recognition_manager_.recognition_started_event().Wait(); // We should wait for a navigation event, raised by the test page JS code // upon the onwebkitspeechchange event, in all cases except when the // speech response is inhibited. if (fake_speech_recognition_manager_.should_send_fake_response()) observer.Wait(); } void RunSpeechRecognitionTest(const char* filename) { // The fake speech input manager would receive the speech input // request and return the test string as recognition result. The test page // then sets the URL fragment as 'pass' if it received the expected string. LoadAndStartSpeechRecognitionTest(filename); EXPECT_EQ("pass", shell()->web_contents()->GetURL().ref()); } // ContentBrowserTest methods. virtual void SetUpInProcessBrowserTestFixture() { fake_speech_recognition_manager_.set_should_send_fake_response(true); speech_recognition_manager_ = &fake_speech_recognition_manager_; // Inject the fake manager factory so that the test result is returned to // the web page. InputTagSpeechDispatcherHost::SetManagerForTests( speech_recognition_manager_); } virtual void TearDownInProcessBrowserTestFixture() { speech_recognition_manager_ = NULL; } FakeSpeechRecognitionManager fake_speech_recognition_manager_; // This is used by the static |fakeManager|, and it is a pointer rather than a // direct instance per the style guide. static SpeechRecognitionManager* speech_recognition_manager_; }; SpeechRecognitionManager* SpeechRecognitionBrowserTest::speech_recognition_manager_ = NULL; // TODO(satish): Once this flakiness has been fixed, add a second test here to // check for sending many clicks in succession to the speech button and verify // that it doesn't cause any crash but works as expected. This should act as the // test for http://crbug.com/59173 // // TODO(satish): Similar to above, once this flakiness has been fixed add // another test here to check that when speech recognition is in progress and // a renderer crashes, we get a call to // SpeechRecognitionManager::CancelAllRequestsWithDelegate. IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, TestBasicRecognition) { RunSpeechRecognitionTest("basic_recognition.html"); EXPECT_TRUE(fake_speech_recognition_manager_.grammar().empty()); } IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, GrammarAttribute) { RunSpeechRecognitionTest("grammar_attribute.html"); EXPECT_EQ("http://example.com/grammar.xml", fake_speech_recognition_manager_.grammar()); } // Flaky on Linux, Windows and Mac http://crbug.com/140765. IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, DISABLED_TestCancelAll) { // The test checks that the cancel-all callback gets issued when a session // is pending, so don't send a fake response. // We are not expecting a navigation event being raised from the JS of the // test page JavaScript in this case. fake_speech_recognition_manager_.set_should_send_fake_response(false); LoadAndStartSpeechRecognitionTest("basic_recognition.html"); // Make the renderer crash. This should trigger // InputTagSpeechDispatcherHost to cancel all pending sessions. NavigateToURL(shell(), GURL(chrome::kChromeUICrashURL)); EXPECT_TRUE(fake_speech_recognition_manager_.did_cancel_all()); } } // namespace content