| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/bind.h" | |
| 6 #include "base/command_line.h" | |
| 7 #include "base/file_path.h" | |
| 8 #include "base/memory/scoped_ptr.h" | |
| 9 #include "base/string_number_conversions.h" | |
| 10 #include "base/synchronization/waitable_event.h" | |
| 11 #include "base/utf_string_conversions.h" | |
| 12 #include "chrome/browser/ui/browser.h" | |
| 13 #include "chrome/browser/ui/browser_tabstrip.h" | |
| 14 #include "chrome/test/base/in_process_browser_test.h" | |
| 15 #include "chrome/test/base/ui_test_utils.h" | |
| 16 #include "content/browser/renderer_host/render_view_host_impl.h" | |
| 17 #include "content/browser/speech/input_tag_speech_dispatcher_host.h" | |
| 18 #include "content/browser/web_contents/web_contents_impl.h" | |
| 19 #include "content/public/browser/notification_types.h" | |
| 20 #include "content/public/browser/speech_recognition_manager.h" | |
| 21 #include "content/public/browser/speech_recognition_session_config.h" | |
| 22 #include "content/public/browser/speech_recognition_session_context.h" | |
| 23 #include "content/public/common/content_switches.h" | |
| 24 #include "content/public/common/speech_recognition_error.h" | |
| 25 #include "content/public/common/speech_recognition_result.h" | |
| 26 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" | |
| 27 | |
| 28 using content::SpeechRecognitionEventListener; | |
| 29 using content::SpeechRecognitionSessionConfig; | |
| 30 using content::SpeechRecognitionSessionContext; | |
| 31 using content::NavigationController; | |
| 32 using content::WebContents; | |
| 33 | |
| 34 namespace speech { | |
| 35 class FakeSpeechRecognitionManager; | |
| 36 } | |
| 37 | |
| 38 namespace speech { | |
| 39 | |
| 40 const char kTestResult[] = "Pictures of the moon"; | |
| 41 | |
| 42 class FakeSpeechRecognitionManager : public content::SpeechRecognitionManager { | |
| 43 public: | |
| 44 FakeSpeechRecognitionManager() | |
| 45 : session_id_(0), | |
| 46 listener_(NULL), | |
| 47 did_cancel_all_(false), | |
| 48 should_send_fake_response_(true), | |
| 49 recognition_started_event_(false, false) { | |
| 50 } | |
| 51 | |
| 52 std::string grammar() { | |
| 53 return grammar_; | |
| 54 } | |
| 55 | |
| 56 bool did_cancel_all() { | |
| 57 return did_cancel_all_; | |
| 58 } | |
| 59 | |
| 60 void set_should_send_fake_response(bool send) { | |
| 61 should_send_fake_response_ = send; | |
| 62 } | |
| 63 | |
| 64 bool should_send_fake_response() { | |
| 65 return should_send_fake_response_; | |
| 66 } | |
| 67 | |
| 68 base::WaitableEvent& recognition_started_event() { | |
| 69 return recognition_started_event_; | |
| 70 } | |
| 71 | |
| 72 // SpeechRecognitionManager methods. | |
| 73 virtual int CreateSession( | |
| 74 const content::SpeechRecognitionSessionConfig& config) OVERRIDE { | |
| 75 VLOG(1) << "FAKE CreateSession invoked."; | |
| 76 EXPECT_EQ(0, session_id_); | |
| 77 EXPECT_EQ(NULL, listener_); | |
| 78 listener_ = config.event_listener; | |
| 79 if (config.grammars.size() > 0) | |
| 80 grammar_ = config.grammars[0].url; | |
| 81 session_ctx_ = config.initial_context; | |
| 82 session_config_ = config; | |
| 83 session_id_ = 1; | |
| 84 return session_id_; | |
| 85 } | |
| 86 | |
| 87 virtual void StartSession(int session_id) OVERRIDE { | |
| 88 VLOG(1) << "FAKE StartSession invoked."; | |
| 89 EXPECT_EQ(session_id, session_id_); | |
| 90 EXPECT_TRUE(listener_ != NULL); | |
| 91 | |
| 92 if (should_send_fake_response_) { | |
| 93 // Give the fake result in a short while. | |
| 94 MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | |
| 95 &FakeSpeechRecognitionManager::SetFakeRecognitionResult, | |
| 96 // This class does not need to be refcounted (typically done by | |
| 97 // PostTask) since it will outlive the test and gets released only | |
| 98 // when the test shuts down. Disabling refcounting here saves a bit | |
| 99 // of unnecessary code and the factory method can return a plain | |
| 100 // pointer below as required by the real code. | |
| 101 base::Unretained(this))); | |
| 102 } | |
| 103 recognition_started_event_.Signal(); | |
| 104 } | |
| 105 | |
| 106 virtual void AbortSession(int session_id) OVERRIDE { | |
| 107 VLOG(1) << "FAKE AbortSession invoked."; | |
| 108 EXPECT_EQ(session_id_, session_id); | |
| 109 session_id_ = 0; | |
| 110 listener_ = NULL; | |
| 111 } | |
| 112 | |
| 113 virtual void StopAudioCaptureForSession(int session_id) OVERRIDE { | |
| 114 VLOG(1) << "StopRecording invoked."; | |
| 115 EXPECT_EQ(session_id_, session_id); | |
| 116 // Nothing to do here since we aren't really recording. | |
| 117 } | |
| 118 | |
| 119 virtual void AbortAllSessionsForListener( | |
| 120 content::SpeechRecognitionEventListener* listener) OVERRIDE { | |
| 121 VLOG(1) << "CancelAllRequestsWithDelegate invoked."; | |
| 122 // listener_ is set to NULL if a fake result was received (see below), so | |
| 123 // check that listener_ matches the incoming parameter only when there is | |
| 124 // no fake result sent. | |
| 125 EXPECT_TRUE(should_send_fake_response_ || listener_ == listener); | |
| 126 did_cancel_all_ = true; | |
| 127 } | |
| 128 | |
| 129 virtual void AbortAllSessionsForRenderView(int render_process_id, | |
| 130 int render_view_id) OVERRIDE { | |
| 131 NOTREACHED(); | |
| 132 } | |
| 133 virtual bool HasAudioInputDevices() OVERRIDE { return true; } | |
| 134 virtual bool IsCapturingAudio() OVERRIDE { return true; } | |
| 135 virtual string16 GetAudioInputDeviceModel() OVERRIDE { return string16(); } | |
| 136 virtual void ShowAudioInputSettings() OVERRIDE {} | |
| 137 | |
| 138 virtual int GetSession(int render_process_id, | |
| 139 int render_view_id, | |
| 140 int request_id) const OVERRIDE { | |
| 141 return session_ctx_.render_process_id == render_process_id && | |
| 142 session_ctx_.render_view_id == render_view_id && | |
| 143 session_ctx_.request_id == request_id; | |
| 144 } | |
| 145 | |
| 146 virtual const SpeechRecognitionSessionConfig& GetSessionConfig( | |
| 147 int session_id) const OVERRIDE { | |
| 148 EXPECT_EQ(session_id, session_id_); | |
| 149 return session_config_; | |
| 150 } | |
| 151 | |
| 152 virtual content::SpeechRecognitionSessionContext GetSessionContext( | |
| 153 int session_id) const OVERRIDE { | |
| 154 EXPECT_EQ(session_id, session_id_); | |
| 155 return session_ctx_; | |
| 156 } | |
| 157 | |
| 158 private: | |
| 159 void SetFakeRecognitionResult() { | |
| 160 if (session_id_) { // Do a check in case we were cancelled.. | |
| 161 VLOG(1) << "Setting fake recognition result."; | |
| 162 listener_->OnAudioEnd(session_id_); | |
| 163 content::SpeechRecognitionResult results; | |
| 164 results.hypotheses.push_back(content::SpeechRecognitionHypothesis( | |
| 165 ASCIIToUTF16(kTestResult), 1.0)); | |
| 166 listener_->OnRecognitionResult(session_id_, results); | |
| 167 listener_->OnRecognitionEnd(session_id_); | |
| 168 session_id_ = 0; | |
| 169 listener_ = NULL; | |
| 170 VLOG(1) << "Finished setting fake recognition result."; | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 int session_id_; | |
| 175 SpeechRecognitionEventListener* listener_; | |
| 176 SpeechRecognitionSessionConfig session_config_; | |
| 177 SpeechRecognitionSessionContext session_ctx_; | |
| 178 std::string grammar_; | |
| 179 bool did_cancel_all_; | |
| 180 bool should_send_fake_response_; | |
| 181 base::WaitableEvent recognition_started_event_; | |
| 182 }; | |
| 183 | |
| 184 class SpeechRecognitionBrowserTest : public InProcessBrowserTest { | |
| 185 public: | |
| 186 // InProcessBrowserTest methods | |
| 187 virtual void SetUpCommandLine(CommandLine* command_line) { | |
| 188 EXPECT_TRUE(!command_line->HasSwitch(switches::kDisableSpeechInput)); | |
| 189 } | |
| 190 | |
| 191 GURL testUrl(const FilePath::CharType* filename) { | |
| 192 const FilePath kTestDir(FILE_PATH_LITERAL("speech")); | |
| 193 return ui_test_utils::GetTestUrl(kTestDir, FilePath(filename)); | |
| 194 } | |
| 195 | |
| 196 protected: | |
| 197 void LoadAndStartSpeechRecognitionTest(const FilePath::CharType* filename) { | |
| 198 // The test page calculates the speech button's coordinate in the page on | |
| 199 // load & sets that coordinate in the URL fragment. We send mouse down & up | |
| 200 // events at that coordinate to trigger speech recognition. | |
| 201 GURL test_url = testUrl(filename); | |
| 202 ui_test_utils::NavigateToURL(browser(), test_url); | |
| 203 | |
| 204 WebKit::WebMouseEvent mouse_event; | |
| 205 mouse_event.type = WebKit::WebInputEvent::MouseDown; | |
| 206 mouse_event.button = WebKit::WebMouseEvent::ButtonLeft; | |
| 207 mouse_event.x = 0; | |
| 208 mouse_event.y = 0; | |
| 209 mouse_event.clickCount = 1; | |
| 210 WebContents* web_contents = chrome::GetActiveWebContents(browser()); | |
| 211 | |
| 212 ui_test_utils::WindowedNotificationObserver observer( | |
| 213 content::NOTIFICATION_LOAD_STOP, | |
| 214 content::Source<NavigationController>(&web_contents->GetController())); | |
| 215 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); | |
| 216 mouse_event.type = WebKit::WebInputEvent::MouseUp; | |
| 217 web_contents->GetRenderViewHost()->ForwardMouseEvent(mouse_event); | |
| 218 fake_speech_recognition_manager_.recognition_started_event().Wait(); | |
| 219 | |
| 220 // We should wait for a navigation event, raised by the test page JS code | |
| 221 // upon the onwebkitspeechchange event, in all cases except when the | |
| 222 // speech response is inhibited. | |
| 223 if (fake_speech_recognition_manager_.should_send_fake_response()) | |
| 224 observer.Wait(); | |
| 225 } | |
| 226 | |
| 227 void RunSpeechRecognitionTest(const FilePath::CharType* filename) { | |
| 228 // The fake speech input manager would receive the speech input | |
| 229 // request and return the test string as recognition result. The test page | |
| 230 // then sets the URL fragment as 'pass' if it received the expected string. | |
| 231 LoadAndStartSpeechRecognitionTest(filename); | |
| 232 | |
| 233 EXPECT_EQ("pass", chrome::GetActiveWebContents(browser())->GetURL().ref()); | |
| 234 } | |
| 235 | |
| 236 // InProcessBrowserTest methods. | |
| 237 virtual void SetUpInProcessBrowserTestFixture() { | |
| 238 fake_speech_recognition_manager_.set_should_send_fake_response(true); | |
| 239 speech_recognition_manager_ = &fake_speech_recognition_manager_; | |
| 240 | |
| 241 // Inject the fake manager factory so that the test result is returned to | |
| 242 // the web page. | |
| 243 InputTagSpeechDispatcherHost::SetManagerForTests( | |
| 244 speech_recognition_manager_); | |
| 245 } | |
| 246 | |
| 247 virtual void TearDownInProcessBrowserTestFixture() { | |
| 248 speech_recognition_manager_ = NULL; | |
| 249 } | |
| 250 | |
| 251 FakeSpeechRecognitionManager fake_speech_recognition_manager_; | |
| 252 | |
| 253 // This is used by the static |fakeManager|, and it is a pointer rather than a | |
| 254 // direct instance per the style guide. | |
| 255 static content::SpeechRecognitionManager* speech_recognition_manager_; | |
| 256 }; | |
| 257 | |
| 258 content::SpeechRecognitionManager* | |
| 259 SpeechRecognitionBrowserTest::speech_recognition_manager_ = NULL; | |
| 260 | |
| 261 // TODO(satish): Once this flakiness has been fixed, add a second test here to | |
| 262 // check for sending many clicks in succession to the speech button and verify | |
| 263 // that it doesn't cause any crash but works as expected. This should act as the | |
| 264 // test for http://crbug.com/59173 | |
| 265 // | |
| 266 // TODO(satish): Similar to above, once this flakiness has been fixed add | |
| 267 // another test here to check that when speech recognition is in progress and | |
| 268 // a renderer crashes, we get a call to | |
| 269 // SpeechRecognitionManager::CancelAllRequestsWithDelegate. | |
| 270 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, TestBasicRecognition) { | |
| 271 RunSpeechRecognitionTest(FILE_PATH_LITERAL("basic_recognition.html")); | |
| 272 EXPECT_TRUE(fake_speech_recognition_manager_.grammar().empty()); | |
| 273 } | |
| 274 | |
| 275 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, GrammarAttribute) { | |
| 276 RunSpeechRecognitionTest(FILE_PATH_LITERAL("grammar_attribute.html")); | |
| 277 EXPECT_EQ("http://example.com/grammar.xml", | |
| 278 fake_speech_recognition_manager_.grammar()); | |
| 279 } | |
| 280 | |
| 281 IN_PROC_BROWSER_TEST_F(SpeechRecognitionBrowserTest, TestCancelAll) { | |
| 282 // The test checks that the cancel-all callback gets issued when a session | |
| 283 // is pending, so don't send a fake response. | |
| 284 // We are not expecting a navigation event being raised from the JS of the | |
| 285 // test page JavaScript in this case. | |
| 286 fake_speech_recognition_manager_.set_should_send_fake_response(false); | |
| 287 | |
| 288 LoadAndStartSpeechRecognitionTest( | |
| 289 FILE_PATH_LITERAL("basic_recognition.html")); | |
| 290 | |
| 291 // Make the renderer crash. This should trigger | |
| 292 // InputTagSpeechDispatcherHost to cancel all pending sessions. | |
| 293 GURL test_url("about:crash"); | |
| 294 ui_test_utils::NavigateToURL(browser(), test_url); | |
| 295 | |
| 296 EXPECT_TRUE(fake_speech_recognition_manager_.did_cancel_all()); | |
| 297 } | |
| 298 | |
| 299 } // namespace speech | |
| OLD | NEW |