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 #ifndef CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_TEST_H_ 6 #define CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_TEST_H_ 7 8 #include <atlbase.h> 9 #include <atlcom.h> 10 #include <string> 11 #include <vector> 12 13 #include "base/files/file_path.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/win/object_watcher.h" 18 #include "chrome_frame/test/chrome_frame_test_utils.h" 19 #include "chrome_frame/test/chrome_frame_ui_test_utils.h" 20 #include "chrome_frame/test/ie_event_sink.h" 21 #include "chrome_frame/test/test_server.h" 22 #include "chrome_frame/test/test_with_web_server.h" 23 #include "chrome_frame/test/win_event_receiver.h" 24 #include "testing/gmock/include/gmock/gmock.h" 25 #include "testing/gtest/include/gtest/gtest.h" 26 #include "third_party/xulrunner-sdk/win/include/accessibility/AccessibleEventId.h" 27 28 namespace chrome_frame_test { 29 30 // Convenience enum for specifying whether a load occurred in IE or CF. 31 enum LoadedInRenderer { 32 IN_IE = 0, 33 IN_CF 34 }; 35 36 // This mocks an IEEventListener, providing methods for expecting certain 37 // sequences of events. 38 class MockIEEventSink : public IEEventListener { 39 public: 40 MockIEEventSink() { 41 CComObject<IEEventSink>::CreateInstance(&event_sink_); 42 event_sink_->AddRef(); 43 } 44 45 ~MockIEEventSink() { 46 Detach(); 47 int reference_count = event_sink_->reference_count(); 48 LOG_IF(ERROR, reference_count != 1) 49 << "Event sink is still referenced externally: ref count = " 50 << reference_count; 51 event_sink_->Release(); 52 } 53 54 // Override IEEventListener methods. 55 MOCK_METHOD7(OnBeforeNavigate2, void (IDispatch* dispatch, // NOLINT 56 VARIANT* url, 57 VARIANT* flags, 58 VARIANT* target_frame_name, 59 VARIANT* post_data, 60 VARIANT* headers, 61 VARIANT_BOOL* cancel)); 62 MOCK_METHOD2(OnNavigateComplete2, void (IDispatch* dispatch, // NOLINT 63 VARIANT* url)); 64 MOCK_METHOD5(OnNewWindow3, void (IDispatch** dispatch, // NOLINT 65 VARIANT_BOOL* cancel, 66 DWORD flags, 67 BSTR url_context, 68 BSTR url)); 69 MOCK_METHOD2(OnNewWindow2, void (IDispatch** dispatch, // NOLINT 70 VARIANT_BOOL* cancel)); 71 MOCK_METHOD5(OnNavigateError, void (IDispatch* dispatch, // NOLINT 72 VARIANT* url, 73 VARIANT* frame_name, 74 VARIANT* status_code, 75 VARIANT* cancel)); 76 MOCK_METHOD2(OnFileDownload, void (VARIANT_BOOL active_doc, // NOLINT 77 VARIANT_BOOL* cancel)); 78 MOCK_METHOD0(OnQuit, void ()); // NOLINT 79 MOCK_METHOD1(OnLoadError, void (const wchar_t* url)); // NOLINT 80 MOCK_METHOD3(OnMessage, void (const wchar_t* message, // NOLINT 81 const wchar_t* origin, 82 const wchar_t* source)); 83 MOCK_METHOD2(OnNewBrowserWindow, void (IDispatch* dispatch, // NOLINT 84 const wchar_t* url)); 85 86 // Convenience OnLoad method which is called once when a page is loaded with 87 // |is_cf| set to whether the renderer is CF or not. 88 MOCK_METHOD2(OnLoad, void (bool is_cf, const wchar_t* url)); // NOLINT 89 90 // Attach |dispatch| to the event sink and begin listening to the source's 91 // events. 92 void Attach(IDispatch* dispatch) { 93 event_sink_->set_listener(this); 94 event_sink_->Attach(dispatch); 95 } 96 97 void Detach() { 98 event_sink_->set_listener(NULL); 99 event_sink_->Uninitialize(); 100 } 101 102 // Expect a normal navigation to |url| to occur in CF or IE. 103 void ExpectNavigation(bool is_cf, const std::wstring& url); 104 105 // Similar as above, but should be used in cases where IE6 with CF may not 106 // generate an OnBeforeNavigate event during the navigation. This occurs 107 // during in-page navigation (via anchors), form submission, window.open 108 // to target "self", and possibly others. 109 void ExpectNavigationOptionalBefore(bool is_cf, const std::wstring& url); 110 111 // Expect a navigation in a new window created by a window.open call to |url|. 112 // |parent_cf| signifies whether the parent frame was loaded in CF, while 113 // |new_window_cf| signifies whether to expect the new page to be loaded in 114 // CF. 115 void ExpectJavascriptWindowOpenNavigation(bool parent_cf, bool new_window_cf, 116 const std::wstring& url); 117 118 // Expect a new window to open. The new event sink will be attached to 119 // |new_window_mock|. 120 void ExpectNewWindow(MockIEEventSink* new_window_mock); 121 122 // Expects any and all navigations. 123 void ExpectAnyNavigations(); 124 125 void ExpectDocumentReadystate(int ready_state); 126 127 IEEventSink* event_sink() { return event_sink_; } 128 129 private: 130 // Override IE's OnDocumentComplete to call our OnLoad, iff it is IE actually 131 // rendering the page. 132 virtual void OnDocumentComplete(IDispatch* dispatch, VARIANT* url); 133 134 // Override CF's OnLoad to call our OnLoad. 135 virtual void OnLoad(const wchar_t* url) { 136 OnLoad(IN_CF, url); 137 } 138 139 // Helper method for expecting navigations. |before_cardinality| specifies 140 // the cardinality for the BeforeNavigate expectation and 141 // |complete_cardinality| specifies the cardinality for the NavigateComplete 142 // expectation. Returns the set of expectations added. 143 // Note: Prefer making a new Expect... method before making this public. 144 testing::ExpectationSet ExpectNavigationCardinality(const std::wstring& url, 145 testing::Cardinality before_cardinality, 146 testing::Cardinality complete_cardinality); 147 148 // It may be necessary to create this on the heap. Otherwise, if the 149 // reference count is greater than zero, a debug assert will be triggered 150 // in the destructor. This happens at least when IE crashes. In that case, 151 // DispEventUnadvise and CoDisconnectObject are not sufficient to decrement 152 // the reference count. 153 // TODO(kkania): Investigate if the above is true. 154 CComObject<IEEventSink>* event_sink_; 155 }; 156 157 // This mocks a PropertyNotifySinkListener, providing methods for 158 // expecting certain sequences of events. 159 class MockPropertyNotifySinkListener : public PropertyNotifySinkListener { 160 public: 161 MockPropertyNotifySinkListener() : cookie_(0), sink_(NULL) { 162 CComObject<PropertyNotifySinkImpl>::CreateInstance(&sink_); 163 sink_->AddRef(); 164 sink_->set_listener(this); 165 } 166 167 ~MockPropertyNotifySinkListener() { 168 Detach(); 169 sink_->set_listener(NULL); 170 LOG_IF(ERROR, sink_->m_dwRef != 1) 171 << "Event sink is still referenced externally: ref count = " 172 << sink_->m_dwRef; 173 sink_->Release(); 174 } 175 176 // Override PropertyNotifySinkListener methods. 177 MOCK_METHOD1(OnChanged, void (DISPID dispid)); // NOLINT 178 179 bool Attach(IUnknown* obj) { 180 DCHECK_EQ(cookie_, 0UL); 181 DCHECK(obj); 182 HRESULT hr = AtlAdvise(obj, sink_->GetUnknown(), IID_IPropertyNotifySink, 183 &cookie_); 184 if (SUCCEEDED(hr)) { 185 event_source_ = obj; 186 } else { 187 LOG(ERROR) << base::StringPrintf("AtlAdvise: 0x%08X", hr); 188 cookie_ = 0; 189 } 190 191 return SUCCEEDED(hr); 192 } 193 194 void Detach() { 195 if (event_source_) { 196 DCHECK_NE(cookie_, 0UL); 197 AtlUnadvise(event_source_, IID_IPropertyNotifySink, cookie_); 198 event_source_.Release(); 199 cookie_ = 0; 200 } 201 } 202 203 private: 204 CComObject<PropertyNotifySinkImpl>* sink_; 205 DWORD cookie_; 206 base::win::ScopedComPtr<IUnknown> event_source_; 207 }; 208 209 // Allows tests to observe when processes exit. 210 class MockObjectWatcherDelegate : public base::win::ObjectWatcher::Delegate { 211 public: 212 // base::win::ObjectWatcher::Delegate implementation 213 MOCK_METHOD1(OnObjectSignaled, void (HANDLE process_handle)); // NOLINT 214 215 virtual ~MockObjectWatcherDelegate() { 216 // Would be nice to free them when OnObjectSignaled is called, too, but 217 // it doesn't seem worth it. 218 for (std::vector<HANDLE>::iterator it = process_handles_.begin(); 219 it != process_handles_.end(); ++it) { 220 ::CloseHandle(*it); 221 } 222 } 223 224 // Registers this instance to watch |process_handle| for termination. 225 void WatchProcess(HANDLE process_handle) { 226 process_handles_.push_back(process_handle); 227 object_watcher_.StartWatching(process_handle, this); 228 } 229 230 // Registers this instance to watch |hwnd|'s owning process for termination. 231 void WatchProcessForHwnd(HWND hwnd) { 232 DWORD pid = 0; 233 ::GetWindowThreadProcessId(hwnd, &pid); 234 EXPECT_TRUE(pid); 235 if (pid != 0) { 236 HANDLE process_handle = ::OpenProcess(SYNCHRONIZE, FALSE, pid); 237 EXPECT_TRUE(process_handle); 238 if (process_handle != NULL) { 239 WatchProcess(process_handle); 240 } 241 } 242 } 243 244 private: 245 std::vector<HANDLE> process_handles_; 246 base::win::ObjectWatcher object_watcher_; 247 }; 248 249 // Mocks a window observer so that tests can detect new windows. 250 class MockWindowObserver : public WindowObserver { 251 public: 252 // WindowObserver implementation 253 MOCK_METHOD1(OnWindowOpen, void (HWND hwnd)); // NOLINT 254 MOCK_METHOD1(OnWindowClose, void (HWND hwnd)); // NOLINT 255 256 void WatchWindow(std::string caption_pattern, std::string class_pattern) { 257 window_watcher_.AddObserver(this, caption_pattern, class_pattern); 258 } 259 260 private: 261 WindowWatchdog window_watcher_; 262 }; 263 264 class MockAccEventObserver : public AccEventObserver { 265 public: 266 MOCK_METHOD1(OnAccDocLoad, void (HWND)); // NOLINT 267 MOCK_METHOD3(OnAccValueChange, void (HWND, AccObject*, // NOLINT 268 const std::wstring&)); 269 MOCK_METHOD1(OnMenuPopup, void (HWND)); // NOLINT 270 MOCK_METHOD2(OnTextCaretMoved, void (HWND, AccObject*)); // NOLINT 271 }; 272 273 // This test fixture provides common methods needed for testing CF 274 // integration with IE. gMock is used to verify that IE is reporting correct 275 // navigational events and MockWebServer is used to verify that the correct 276 // requests are going out. 277 class MockIEEventSinkTest { 278 public: 279 MockIEEventSinkTest(); 280 MockIEEventSinkTest(int port, const std::wstring& address, 281 const base::FilePath& root_dir); 282 283 ~MockIEEventSinkTest() { 284 // Detach manually here so that it occurs before |last_resort_close_ie_| 285 // is destroyed. 286 ie_mock_.Detach(); 287 } 288 289 // Launches IE as a COM server and sets |ie_mock_| as the event sink, then 290 // navigates to the given url. Then the timed message loop is run until 291 // |ie_mock_| receives OnQuit or the timeout is exceeded. 292 void LaunchIEAndNavigate(const std::wstring& url); 293 294 // Same as above but allows the timeout to be specified. 295 void LaunchIENavigateAndLoop(const std::wstring& url, 296 base::TimeDelta timeout); 297 298 // Returns the url for the test file given. |relative_path| should be 299 // relative to the test data directory. 300 std::wstring GetTestUrl(const std::wstring& relative_path); 301 302 // Returns the absolute FilePath for the test file given. |relative_path| 303 // should be relative to the test data directory. 304 base::FilePath GetTestFilePath(const std::wstring& relative_path); 305 306 // Returns the url for an html page just containing some text. Iff |use_cf| 307 // is true, the chrome_frame meta tag will be included too. 308 std::wstring GetSimplePageUrl() { 309 return GetTestUrl(L"simple.html"); 310 } 311 312 // Returns the title of the html page at |GetSimplePageUrl()|. 313 std::wstring GetSimplePageTitle() { 314 return L"simple web page"; 315 } 316 317 // Returns the url for an html page just containing one link to the simple 318 // page mentioned above. 319 std::wstring GetLinkPageUrl() { 320 return GetTestUrl(L"link.html"); 321 } 322 323 // Returns the title of the html page at |GetLinkPageUrl()|. 324 std::wstring GetLinkPageTitle() { 325 return L"link"; 326 } 327 328 // Returns the url for an html page containing several anchors pointing 329 // to different parts of the page. |index| specifies what fragment to 330 // append to the url. If zero, no fragment is appended. The highest fragment 331 // is #a4. 332 std::wstring GetAnchorPageUrl(int index) { 333 DCHECK_LT(index, 5); 334 std::wstring base_name = L"anchor.html"; 335 if (index > 0) 336 base_name += std::wstring(L"#a") + base::IntToString16(index); 337 return GetTestUrl(base_name); 338 } 339 340 // Returns the title of the html page at |GetAnchorPageUrl()|. 341 std::wstring GetAnchorPageTitle() { 342 return L"Chrome Frame Test"; 343 } 344 345 // Returns the url for an html page that will, when clicked, open a new window 346 // to |target|. 347 std::wstring GetWindowOpenUrl(const wchar_t* target) { 348 return GetTestUrl(std::wstring(L"window_open.html?").append(target)); 349 } 350 351 // Returns the title of the html page at |GetWindowOpenUrl()|. 352 std::wstring GetWindowOpenTitle() { 353 return L"window open"; 354 } 355 356 protected: 357 CloseIeAtEndOfScope last_resort_close_ie_; 358 chrome_frame_test::TimedMsgLoop loop_; 359 testing::StrictMock<MockIEEventSink> ie_mock_; 360 testing::StrictMock<MockWebServer> server_mock_; 361 scoped_refptr<HungCOMCallDetector> hung_call_detector_; 362 private: 363 DISALLOW_COPY_AND_ASSIGN(MockIEEventSinkTest); 364 }; 365 366 } // namespace chrome_frame_test 367 368 #endif // CHROME_FRAME_TEST_MOCK_IE_EVENT_SINK_TEST_H_ 369