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 "chrome_frame/test/ie_event_sink.h"
6
7#include <shlguid.h>
8#include <shobjidl.h>
9
10#include <map>
11#include <utility>
12
13#include "base/lazy_instance.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/string_piece.h"
16#include "base/strings/string_util.h"
17#include "base/strings/stringprintf.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/test/test_timeouts.h"
20#include "base/time/time.h"
21#include "base/win/scoped_bstr.h"
22#include "base/win/scoped_handle.h"
23#include "base/win/scoped_variant.h"
24#include "chrome_frame/test/chrome_frame_test_utils.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27using base::win::ScopedBstr;
28
29namespace {
30
31// A lookup table from DISPID to DWebBrowserEvents and/or DWebBrowserEvents2
32// method name.
33class DispIdNameTable {
34 public:
35  DispIdNameTable();
36  ~DispIdNameTable();
37
38  // Returns the method name corresponding to |dispid| or, if none is known,
39  // the string "DISPID |dispid|".
40  std::string Lookup(DISPID dispid) const;
41
42 private:
43  std::map<DISPID,const char*> dispid_to_name_;
44  DISALLOW_COPY_AND_ASSIGN(DispIdNameTable);
45};
46
47DispIdNameTable::DispIdNameTable() {
48  static const struct {
49    DISPID dispid;
50    const char* name;
51  } kIdToName[] = {
52    // DWebBrowserEvents
53    { 100, "BeforeNavigate" },
54    { 101, "NavigateComplete" },
55    { 102, "StatusTextChange" },
56    { 108, "ProgressChange" },
57    { 104, "DownloadComplete" },
58    { 105, "CommandStateChange" },
59    { 106, "DownloadBegin" },
60    { 107, "NewWindow" },
61    { 113, "TitleChange" },
62    { 200, "FrameBeforeNavigate" },
63    { 201, "FrameNavigateComplete" },
64    { 204, "FrameNewWindow" },
65    { 103, "Quit" },
66    { 109, "WindowMove" },
67    { 110, "WindowResize" },
68    { 111, "WindowActivate" },
69    { 112, "PropertyChange" },
70    // DWebBrowserEvents2
71    { 250, "BeforeNavigate2" },
72    { 251, "NewWindow2" },
73    { 252, "NavigateComplete2" },
74    { 259, "DocumentComplete" },
75    { 253, "OnQuit" },
76    { 254, "OnVisible" },
77    { 255, "OnToolBar" },
78    { 256, "OnMenuBar" },
79    { 257, "OnStatusBar" },
80    { 258, "OnFullScreen" },
81    { 260, "OnTheaterMode" },
82    { 262, "WindowSetResizable" },
83    { 264, "WindowSetLeft" },
84    { 265, "WindowSetTop" },
85    { 266, "WindowSetWidth" },
86    { 267, "WindowSetHeight" },
87    { 263, "WindowClosing" },
88    { 268, "ClientToHostWindow" },
89    { 269, "SetSecureLockIcon" },
90    { 270, "FileDownload" },
91    { 271, "NavigateError" },
92    { 225, "PrintTemplateInstantiation" },
93    { 226, "PrintTemplateTeardown" },
94    { 227, "UpdatePageStatus" },
95    { 272, "PrivacyImpactedStateChange" },
96    { 273, "NewWindow3" },
97    { 282, "SetPhishingFilterStatus" },
98    { 283, "WindowStateChanged" },
99    { 284, "NewProcess" },
100    { 285, "ThirdPartyUrlBlocked" },
101    { 286, "RedirectXDomainBlocked" },
102    // Present in ExDispid.h but not ExDisp.idl
103    { 114, "TitleIconChange" },
104    { 261, "OnAddressBar" },
105    { 281, "ViewUpdate" },
106  };
107  size_t index_of_duplicate = 0;
108  DISPID duplicate_dispid = 0;
109  for (size_t i = 0; i < arraysize(kIdToName); ++i) {
110    if (!dispid_to_name_.insert(std::make_pair(kIdToName[i].dispid,
111                                               kIdToName[i].name)).second &&
112        index_of_duplicate == 0) {
113      index_of_duplicate = i;
114      duplicate_dispid = kIdToName[i].dispid;
115    }
116  }
117  DCHECK_EQ(static_cast<size_t>(0), index_of_duplicate)
118      << "Duplicate name for DISPID " << duplicate_dispid
119      << " at kIdToName[" << index_of_duplicate << "]";
120}
121
122DispIdNameTable::~DispIdNameTable() {
123}
124
125std::string DispIdNameTable::Lookup(DISPID dispid) const {
126  std::map<DISPID,const char*>::const_iterator it =
127      dispid_to_name_.find(dispid);
128  if (it != dispid_to_name_.end())
129    return it->second;
130  return std::string("DISPID ").append(base::IntToString(dispid));
131}
132
133base::LazyInstance<DispIdNameTable> g_dispIdToName = LAZY_INSTANCE_INITIALIZER;
134
135}  // namespace
136
137namespace chrome_frame_test {
138
139const int kDefaultWaitForIEToTerminateMs = 10 * 1000;
140
141_ATL_FUNC_INFO IEEventSink::kNavigateErrorInfo = {
142  CC_STDCALL, VT_EMPTY, 5, {
143    VT_DISPATCH,
144    VT_VARIANT | VT_BYREF,
145    VT_VARIANT | VT_BYREF,
146    VT_VARIANT | VT_BYREF,
147    VT_BOOL | VT_BYREF,
148  }
149};
150
151_ATL_FUNC_INFO IEEventSink::kNavigateComplete2Info = {
152  CC_STDCALL, VT_EMPTY, 2, {
153    VT_DISPATCH,
154    VT_VARIANT | VT_BYREF
155  }
156};
157
158_ATL_FUNC_INFO IEEventSink::kBeforeNavigate2Info = {
159  CC_STDCALL, VT_EMPTY, 7, {
160    VT_DISPATCH,
161    VT_VARIANT | VT_BYREF,
162    VT_VARIANT | VT_BYREF,
163    VT_VARIANT | VT_BYREF,
164    VT_VARIANT | VT_BYREF,
165    VT_VARIANT | VT_BYREF,
166    VT_BOOL | VT_BYREF
167  }
168};
169
170_ATL_FUNC_INFO IEEventSink::kNewWindow2Info = {
171  CC_STDCALL, VT_EMPTY, 2, {
172    VT_DISPATCH | VT_BYREF,
173    VT_BOOL | VT_BYREF,
174  }
175};
176
177_ATL_FUNC_INFO IEEventSink::kNewWindow3Info = {
178  CC_STDCALL, VT_EMPTY, 5, {
179    VT_DISPATCH | VT_BYREF,
180    VT_BOOL | VT_BYREF,
181    VT_UINT,
182    VT_BSTR,
183    VT_BSTR
184  }
185};
186
187_ATL_FUNC_INFO IEEventSink::kVoidMethodInfo = {
188    CC_STDCALL, VT_EMPTY, 0, {NULL}};
189
190_ATL_FUNC_INFO IEEventSink::kDocumentCompleteInfo = {
191  CC_STDCALL, VT_EMPTY, 2, {
192    VT_DISPATCH,
193    VT_VARIANT | VT_BYREF
194  }
195};
196
197_ATL_FUNC_INFO IEEventSink::kFileDownloadInfo = {
198  CC_STDCALL, VT_EMPTY, 2, {
199    VT_BOOL,
200    VT_BOOL | VT_BYREF
201  }
202};
203
204bool IEEventSink::abnormal_shutdown_ = false;
205
206IEEventSink::IEEventSink()
207    : onmessage_(this, &IEEventSink::OnMessage),
208      onloaderror_(this, &IEEventSink::OnLoadError),
209      onload_(this, &IEEventSink::OnLoad),
210      listener_(NULL),
211      ie_process_id_(0),
212      did_receive_on_quit_(false) {
213}
214
215IEEventSink::~IEEventSink() {
216  Uninitialize();
217}
218
219void IEEventSink::SetAbnormalShutdown(bool abnormal_shutdown) {
220  abnormal_shutdown_ = abnormal_shutdown;
221}
222
223// IEEventSink member defines
224void IEEventSink::Attach(IDispatch* browser_disp) {
225  EXPECT_TRUE(NULL != browser_disp);
226  if (browser_disp) {
227    EXPECT_HRESULT_SUCCEEDED(web_browser2_.QueryFrom(browser_disp));
228    EXPECT_HRESULT_SUCCEEDED(Attach(web_browser2_.get()));
229  }
230}
231
232HRESULT IEEventSink::Attach(IWebBrowser2* browser) {
233  HRESULT result = E_INVALIDARG;
234  if (browser) {
235    web_browser2_ = browser;
236    FindIEProcessId();
237    result = DispEventAdvise(web_browser2_, &DIID_DWebBrowserEvents2);
238  }
239  return result;
240}
241
242void IEEventSink::Uninitialize() {
243  if (!abnormal_shutdown_) {
244    DisconnectFromChromeFrame();
245    if (web_browser2_.get()) {
246      if (m_dwEventCookie != 0xFEFEFEFE) {
247        DispEventUnadvise(web_browser2_);
248        CoDisconnectObject(this, 0);
249      }
250
251      if (!did_receive_on_quit_) {
252        // Log the browser window url for debugging purposes.
253        ScopedBstr browser_url;
254        web_browser2_->get_LocationURL(browser_url.Receive());
255        std::wstring browser_url_wstring;
256        browser_url_wstring.assign(browser_url, browser_url.Length());
257        std::string browser_url_string = WideToUTF8(browser_url_wstring);
258        LOG(ERROR) << "OnQuit was not received for browser with url "
259                   << browser_url_string;
260        web_browser2_->Quit();
261      }
262
263      base::win::ScopedHandle process;
264      process.Set(OpenProcess(SYNCHRONIZE, FALSE, ie_process_id_));
265      web_browser2_.Release();
266
267      if (!process.IsValid()) {
268        LOG_IF(WARNING, !process.IsValid())
269            << base::StringPrintf("OpenProcess failed: %i", ::GetLastError());
270        return;
271      }
272      // IE may not have closed yet. Wait here for the process to finish.
273      // This is necessary at least on some browser/platform configurations.
274      WaitForSingleObject(process, kDefaultWaitForIEToTerminateMs);
275    }
276  } else {
277    LOG(ERROR) << "Terminating hung IE process";
278  }
279  chrome_frame_test::KillProcesses(chrome_frame_test::kIEImageName, 0,
280                                   !abnormal_shutdown_);
281  chrome_frame_test::KillProcesses(chrome_frame_test::kIEBrokerImageName, 0,
282                                   !abnormal_shutdown_);
283}
284
285bool IEEventSink::IsCFRendering() {
286  DCHECK(web_browser2_);
287
288  if (web_browser2_) {
289    base::win::ScopedComPtr<IDispatch> doc;
290    web_browser2_->get_Document(doc.Receive());
291    if (doc) {
292      // Detect if CF is rendering based on whether the document is a
293      // ChromeActiveDocument. Detecting based on hwnd is problematic as
294      // the CF Active Document window may not have been created yet.
295      base::win::ScopedComPtr<IChromeFrame> chrome_frame;
296      chrome_frame.QueryFrom(doc);
297      return chrome_frame.get();
298    }
299  }
300  return false;
301}
302
303void IEEventSink::PostMessageToCF(const std::wstring& message,
304                                  const std::wstring& target) {
305  EXPECT_TRUE(chrome_frame_ != NULL);
306  if (!chrome_frame_)
307    return;
308  ScopedBstr message_bstr(message.c_str());
309  base::win::ScopedVariant target_variant(target.c_str());
310  EXPECT_HRESULT_SUCCEEDED(
311      chrome_frame_->postMessage(message_bstr, target_variant));
312}
313
314void IEEventSink::SetFocusToRenderer() {
315  simulate_input::SetKeyboardFocusToWindow(GetRendererWindow());
316}
317
318void IEEventSink::SendKeys(const char* input_string) {
319  HWND window = GetRendererWindow();
320  simulate_input::SetKeyboardFocusToWindow(window);
321  const base::TimeDelta kMessageSleep = TestTimeouts::tiny_timeout();
322  const base::StringPiece codes(input_string);
323  for (size_t i = 0; i < codes.length(); ++i) {
324    char character = codes[i];
325    UINT virtual_key = 0;
326
327    if (character >= 'a' && character <= 'z') {
328      // VK_A - VK_Z are ASCII 'A' - 'Z'.
329      virtual_key = 'A' + (character - 'a');
330    } else if (character >= '0' && character <= '9') {
331      // VK_0 - VK_9 are ASCII '0' - '9'.
332      virtual_key = character;
333    } else {
334      FAIL() << "Character value out of range at position " << i
335             << " of string \"" << input_string << "\"";
336    }
337
338    UINT scan_code = MapVirtualKey(virtual_key, MAPVK_VK_TO_VSC);
339    EXPECT_NE(0U, scan_code) << "No translation for virtual key "
340                             << virtual_key << " for character at position "
341                             << i << " of string \"" << input_string << "\"";
342
343    ::PostMessage(window, WM_KEYDOWN,
344                  virtual_key, MAKELPARAM(1, scan_code));
345    base::PlatformThread::Sleep(kMessageSleep);
346    ::PostMessage(window, WM_KEYUP,
347                  virtual_key, MAKELPARAM(1, scan_code | KF_UP | KF_REPEAT));
348    base::PlatformThread::Sleep(kMessageSleep);
349  }
350}
351
352void IEEventSink::SendMouseClick(int x, int y,
353                                 simulate_input::MouseButton button) {
354  simulate_input::SendMouseClick(GetRendererWindow(), x, y, button);
355}
356
357void IEEventSink::ExpectRendererWindowHasFocus() {
358  HWND renderer_window = GetRendererWindow();
359  EXPECT_TRUE(IsWindow(renderer_window));
360
361  DWORD renderer_thread = 0;
362  DWORD renderer_process = 0;
363  renderer_thread = GetWindowThreadProcessId(renderer_window,
364                                             &renderer_process);
365
366  ASSERT_TRUE(AttachThreadInput(GetCurrentThreadId(), renderer_thread, TRUE));
367  HWND focus_window = GetFocus();
368  EXPECT_EQ(renderer_window, focus_window);
369  EXPECT_TRUE(AttachThreadInput(GetCurrentThreadId(), renderer_thread, FALSE));
370}
371
372void IEEventSink::ExpectAddressBarUrl(
373    const std::wstring& expected_url) {
374  DCHECK(web_browser2_);
375  if (web_browser2_) {
376    ScopedBstr address_bar_url;
377    EXPECT_EQ(S_OK, web_browser2_->get_LocationURL(address_bar_url.Receive()));
378    EXPECT_EQ(expected_url, std::wstring(address_bar_url));
379  }
380}
381
382void IEEventSink::Exec(const GUID* cmd_group_guid, DWORD command_id,
383                               DWORD cmd_exec_opt, VARIANT* in_args,
384                               VARIANT* out_args) {
385  base::win::ScopedComPtr<IOleCommandTarget> shell_browser_cmd_target;
386  DoQueryService(SID_STopLevelBrowser, web_browser2_,
387                 shell_browser_cmd_target.Receive());
388  ASSERT_TRUE(NULL != shell_browser_cmd_target);
389  EXPECT_HRESULT_SUCCEEDED(shell_browser_cmd_target->Exec(cmd_group_guid,
390      command_id, cmd_exec_opt, in_args, out_args));
391}
392
393HWND IEEventSink::GetBrowserWindow() {
394  HWND browser_window = NULL;
395  web_browser2_->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&browser_window));
396  EXPECT_TRUE(::IsWindow(browser_window));
397  return browser_window;
398}
399
400HWND IEEventSink::GetRendererWindow() {
401  HWND renderer_window = NULL;
402  if (IsCFRendering()) {
403    DCHECK(chrome_frame_);
404    base::win::ScopedComPtr<IOleWindow> ole_window;
405    ole_window.QueryFrom(chrome_frame_);
406    EXPECT_TRUE(ole_window.get());
407
408    if (ole_window) {
409      HWND activex_window = NULL;
410      ole_window->GetWindow(&activex_window);
411      EXPECT_TRUE(IsWindow(activex_window));
412
413      wchar_t class_name[MAX_PATH] = {0};
414      HWND child_window = NULL;
415      // chrome tab window is the first (and the only) child of activex
416      for (HWND first_child = activex_window; ::IsWindow(first_child);
417           first_child = ::GetWindow(first_child, GW_CHILD)) {
418        child_window = first_child;
419        GetClassName(child_window, class_name, arraysize(class_name));
420#if defined(USE_AURA)
421        static const wchar_t kWndClassPrefix[] = L"Chrome_WidgetWin_";
422#else
423        static const wchar_t kWndClassPrefix[] = L"Chrome_RenderWidgetHostHWND";
424#endif
425        if (!_wcsnicmp(class_name, kWndClassPrefix, wcslen(kWndClassPrefix))) {
426          renderer_window = child_window;
427          break;
428        }
429      }
430    }
431  } else {
432    DCHECK(web_browser2_);
433    base::win::ScopedComPtr<IDispatch> doc;
434    HRESULT hr = web_browser2_->get_Document(doc.Receive());
435    EXPECT_HRESULT_SUCCEEDED(hr);
436    EXPECT_TRUE(doc);
437    if (doc) {
438      base::win::ScopedComPtr<IOleWindow> ole_window;
439      ole_window.QueryFrom(doc);
440      EXPECT_TRUE(ole_window);
441      if (ole_window) {
442        ole_window->GetWindow(&renderer_window);
443      }
444    }
445  }
446
447  EXPECT_TRUE(::IsWindow(renderer_window));
448  return renderer_window;
449}
450
451HWND IEEventSink::GetRendererWindowSafe() {
452  HWND renderer_window = NULL;
453  if (IsCFRendering()) {
454    DCHECK(chrome_frame_);
455    base::win::ScopedComPtr<IOleWindow> ole_window;
456    ole_window.QueryFrom(chrome_frame_);
457
458    if (ole_window) {
459      HWND activex_window = NULL;
460      ole_window->GetWindow(&activex_window);
461
462      // chrome tab window is the first (and the only) child of activex
463      for (HWND first_child = activex_window; ::IsWindow(first_child);
464           first_child = ::GetWindow(first_child, GW_CHILD)) {
465        renderer_window = first_child;
466      }
467      wchar_t class_name[MAX_PATH] = {0};
468      GetClassName(renderer_window, class_name, arraysize(class_name));
469      if (_wcsicmp(class_name, L"Chrome_RenderWidgetHostHWND") != 0)
470        renderer_window = NULL;
471    }
472  } else {
473    DCHECK(web_browser2_);
474    base::win::ScopedComPtr<IDispatch> doc;
475    web_browser2_->get_Document(doc.Receive());
476    if (doc) {
477      base::win::ScopedComPtr<IOleWindow> ole_window;
478      ole_window.QueryFrom(doc);
479      if (ole_window) {
480        ole_window->GetWindow(&renderer_window);
481      }
482    }
483  }
484  if (!::IsWindow(renderer_window))
485    renderer_window = NULL;
486  return renderer_window;
487}
488
489HRESULT IEEventSink::LaunchIEAndNavigate(const std::wstring& navigate_url,
490                                         IEEventListener* listener) {
491  listener_ = listener;
492  HRESULT hr = LaunchIEAsComServer(web_browser2_.Receive());
493  if (SUCCEEDED(hr)) {
494    web_browser2_->put_Visible(VARIANT_TRUE);
495    hr = Attach(web_browser2_);
496    if (SUCCEEDED(hr)) {
497      hr = Navigate(navigate_url);
498      if (FAILED(hr)) {
499        LOG(ERROR) << "Failed to navigate IE to " << navigate_url << ", hr = 0x"
500                   << std::hex << hr;
501      }
502    } else {
503      LOG(ERROR) << "Failed to attach to web browser event sink for "
504                 << navigate_url << ", hr = 0x" << std::hex << hr;
505    }
506  } else {
507    LOG(ERROR) << "Failed to Launch IE for " << navigate_url << ", hr = 0x"
508               << std::hex << hr;
509  }
510
511  return hr;
512}
513
514HRESULT IEEventSink::Navigate(const std::wstring& navigate_url) {
515  VARIANT empty = base::win::ScopedVariant::kEmptyVariant;
516  base::win::ScopedVariant url;
517  url.Set(navigate_url.c_str());
518
519  HRESULT hr = S_OK;
520  hr = web_browser2_->Navigate2(url.AsInput(), &empty, &empty, &empty, &empty);
521  EXPECT_TRUE(hr == S_OK);
522  return hr;
523}
524
525HRESULT IEEventSink::CloseWebBrowser() {
526  if (!web_browser2_)
527    return E_FAIL;
528
529  DisconnectFromChromeFrame();
530  EXPECT_HRESULT_SUCCEEDED(web_browser2_->Quit());
531  return S_OK;
532}
533
534void IEEventSink::Refresh() {
535  base::win::ScopedVariant refresh_level(REFRESH_COMPLETELY);
536  web_browser2_->Refresh2(refresh_level.AsInput());
537}
538
539// private methods
540void IEEventSink::ConnectToChromeFrame() {
541  DCHECK(web_browser2_);
542  if (chrome_frame_.get())
543    return;
544  base::win::ScopedComPtr<IShellBrowser> shell_browser;
545  DoQueryService(SID_STopLevelBrowser, web_browser2_,
546                 shell_browser.Receive());
547
548  if (shell_browser) {
549    base::win::ScopedComPtr<IShellView> shell_view;
550    shell_browser->QueryActiveShellView(shell_view.Receive());
551    if (shell_view) {
552      shell_view->GetItemObject(SVGIO_BACKGROUND, __uuidof(IChromeFrame),
553           reinterpret_cast<void**>(chrome_frame_.Receive()));
554    }
555
556    if (chrome_frame_) {
557      base::win::ScopedVariant onmessage(onmessage_.ToDispatch());
558      base::win::ScopedVariant onloaderror(onloaderror_.ToDispatch());
559      base::win::ScopedVariant onload(onload_.ToDispatch());
560      EXPECT_HRESULT_SUCCEEDED(chrome_frame_->put_onmessage(onmessage));
561      EXPECT_HRESULT_SUCCEEDED(chrome_frame_->put_onloaderror(onloaderror));
562      EXPECT_HRESULT_SUCCEEDED(chrome_frame_->put_onload(onload));
563    }
564  }
565}
566
567void IEEventSink::DisconnectFromChromeFrame() {
568  if (chrome_frame_) {
569    // Use a local ref counted copy of the IChromeFrame interface as the
570    // outgoing calls could cause the interface to be deleted due to a message
571    // pump running in the context of the outgoing call.
572    base::win::ScopedComPtr<IChromeFrame> chrome_frame(chrome_frame_);
573    chrome_frame_.Release();
574    base::win::ScopedVariant dummy(static_cast<IDispatch*>(NULL));
575    chrome_frame->put_onmessage(dummy);
576    chrome_frame->put_onload(dummy);
577    chrome_frame->put_onloaderror(dummy);
578  }
579}
580
581void IEEventSink::FindIEProcessId() {
582  HWND hwnd = NULL;
583  web_browser2_->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&hwnd));
584  EXPECT_TRUE(::IsWindow(hwnd));
585  if (::IsWindow(hwnd))
586    ::GetWindowThreadProcessId(hwnd, &ie_process_id_);
587  EXPECT_NE(static_cast<DWORD>(0), ie_process_id_);
588}
589
590// Event callbacks
591STDMETHODIMP_(void) IEEventSink::OnDownloadBegin() {
592  if (listener_)
593    listener_->OnDownloadBegin();
594}
595
596STDMETHODIMP_(void) IEEventSink::OnNewWindow2(IDispatch** dispatch,
597                                              VARIANT_BOOL* s) {
598  VLOG(1) << __FUNCTION__;
599
600  EXPECT_TRUE(dispatch);
601  if (!dispatch)
602    return;
603
604  if (listener_)
605    listener_->OnNewWindow2(dispatch, s);
606
607  // Note that |dispatch| is an [in/out] argument. IE is asking listeners if
608  // they want to use a IWebBrowser2 of their choice for the new window.
609  // Since we need to listen on events on the new browser, we create one
610  // if needed.
611  if (!*dispatch) {
612    base::win::ScopedComPtr<IDispatch> new_browser;
613    HRESULT hr = new_browser.CreateInstance(CLSID_InternetExplorer, NULL,
614                                            CLSCTX_LOCAL_SERVER);
615    DCHECK(SUCCEEDED(hr) && new_browser);
616    *dispatch = new_browser.Detach();
617  }
618
619  if (*dispatch && listener_)
620    listener_->OnNewBrowserWindow(*dispatch, ScopedBstr());
621}
622
623STDMETHODIMP_(void) IEEventSink::OnNavigateError(IDispatch* dispatch,
624    VARIANT* url, VARIANT* frame_name, VARIANT* status_code, VARIANT* cancel) {
625  VLOG(1) << __FUNCTION__;
626  if (listener_)
627    listener_->OnNavigateError(dispatch, url, frame_name, status_code, cancel);
628}
629
630STDMETHODIMP IEEventSink::OnBeforeNavigate2(
631    IDispatch* dispatch, VARIANT* url, VARIANT* flags,
632    VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers,
633    VARIANT_BOOL* cancel) {
634  VLOG(1) << __FUNCTION__ << " "
635          << base::StringPrintf("%ls - 0x%08X", url->bstrVal, this);
636  // Reset any existing reference to chrome frame since this is a new
637  // navigation.
638  DisconnectFromChromeFrame();
639  if (listener_)
640    listener_->OnBeforeNavigate2(dispatch, url, flags, target_frame_name,
641                                 post_data, headers, cancel);
642  return S_OK;
643}
644
645STDMETHODIMP_(void) IEEventSink::OnNavigateComplete2(
646    IDispatch* dispatch, VARIANT* url) {
647  VLOG(1) << __FUNCTION__;
648  ConnectToChromeFrame();
649  if (listener_)
650    listener_->OnNavigateComplete2(dispatch, url);
651}
652
653STDMETHODIMP_(void) IEEventSink::OnDocumentComplete(
654    IDispatch* dispatch, VARIANT* url) {
655  VLOG(1) << __FUNCTION__;
656  EXPECT_TRUE(url);
657  if (!url)
658    return;
659  if (listener_)
660    listener_->OnDocumentComplete(dispatch, url);
661}
662
663STDMETHODIMP_(void) IEEventSink::OnFileDownload(
664    VARIANT_BOOL active_doc, VARIANT_BOOL* cancel) {
665  VLOG(1) << __FUNCTION__ << " "
666          << base::StringPrintf(" 0x%08X ad=%i", this, active_doc);
667  if (listener_) {
668    listener_->OnFileDownload(active_doc, cancel);
669  } else {
670    *cancel = VARIANT_TRUE;
671  }
672}
673
674STDMETHODIMP_(void) IEEventSink::OnNewWindow3(
675    IDispatch** dispatch, VARIANT_BOOL* cancel, DWORD flags, BSTR url_context,
676    BSTR url) {
677  VLOG(1) << __FUNCTION__;
678  EXPECT_TRUE(dispatch);
679  if (!dispatch)
680    return;
681
682  if (listener_)
683    listener_->OnNewWindow3(dispatch, cancel, flags, url_context, url);
684
685  // Note that |dispatch| is an [in/out] argument. IE is asking listeners if
686  // they want to use a IWebBrowser2 of their choice for the new window.
687  // Since we need to listen on events on the new browser, we create one
688  // if needed.
689  if (!*dispatch) {
690    base::win::ScopedComPtr<IDispatch> new_browser;
691    HRESULT hr = new_browser.CreateInstance(CLSID_InternetExplorer, NULL,
692                                            CLSCTX_LOCAL_SERVER);
693    DCHECK(SUCCEEDED(hr) && new_browser);
694    *dispatch = new_browser.Detach();
695  }
696
697  if (*dispatch && listener_)
698    listener_->OnNewBrowserWindow(*dispatch, url);
699}
700
701STDMETHODIMP_(void) IEEventSink::OnQuit() {
702  VLOG(1) << __FUNCTION__;
703
704  did_receive_on_quit_ = true;
705
706  DispEventUnadvise(web_browser2_);
707  CoDisconnectObject(this, 0);
708
709  if (listener_)
710    listener_->OnQuit();
711}
712
713STDMETHODIMP IEEventSink::Invoke(DISPID dispid, REFIID riid, LCID lcid,
714                                 WORD flags, DISPPARAMS* params,
715                                 VARIANT* result, EXCEPINFO* except_info,
716                                 UINT* arg_error) {
717  VLOG(1) << __FUNCTION__ << L" event: " << g_dispIdToName.Get().Lookup(dispid);
718  return DispEventsImpl::Invoke(dispid, riid, lcid, flags, params, result,
719                                except_info, arg_error);
720}
721
722HRESULT IEEventSink::OnLoad(const VARIANT* param) {
723  VLOG(1) << __FUNCTION__ << " " << param->bstrVal;
724  base::win::ScopedVariant stack_object(*param);
725  if (chrome_frame_) {
726    if (listener_)
727      listener_->OnLoad(param->bstrVal);
728  } else {
729    LOG(WARNING) << "Invalid chrome frame pointer";
730  }
731  return S_OK;
732}
733
734HRESULT IEEventSink::OnLoadError(const VARIANT* param) {
735  VLOG(1) << __FUNCTION__ << " " << param->bstrVal;
736  if (chrome_frame_) {
737    if (listener_)
738      listener_->OnLoadError(param->bstrVal);
739  } else {
740    LOG(WARNING) << "Invalid chrome frame pointer";
741  }
742  return S_OK;
743}
744
745HRESULT IEEventSink::OnMessage(const VARIANT* param) {
746  VLOG(1) << __FUNCTION__ << " " << param;
747  if (!chrome_frame_.get()) {
748    LOG(WARNING) << "Invalid chrome frame pointer";
749    return S_OK;
750  }
751
752  base::win::ScopedVariant data, origin, source;
753  if (param && (V_VT(param) == VT_DISPATCH)) {
754    wchar_t* properties[] = { L"data", L"origin", L"source" };
755    const int prop_count = arraysize(properties);
756    DISPID ids[prop_count] = {0};
757
758    HRESULT hr = param->pdispVal->GetIDsOfNames(IID_NULL, properties,
759        prop_count, LOCALE_SYSTEM_DEFAULT, ids);
760    if (SUCCEEDED(hr)) {
761      DISPPARAMS params = { 0 };
762      EXPECT_HRESULT_SUCCEEDED(param->pdispVal->Invoke(ids[0], IID_NULL,
763          LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params,
764          data.Receive(), NULL, NULL));
765      EXPECT_HRESULT_SUCCEEDED(param->pdispVal->Invoke(ids[1], IID_NULL,
766          LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params,
767          origin.Receive(), NULL, NULL));
768      EXPECT_HRESULT_SUCCEEDED(param->pdispVal->Invoke(ids[2], IID_NULL,
769          LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params,
770          source.Receive(), NULL, NULL));
771    }
772  }
773
774  if (listener_)
775    listener_->OnMessage(V_BSTR(&data), V_BSTR(&origin), V_BSTR(&source));
776  return S_OK;
777}
778
779}  // namespace chrome_frame_test
780