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/chrome_frame_automation.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/command_line.h"
11#include "base/compiler_specific.h"
12#include "base/debug/trace_event.h"
13#include "base/file_version_info.h"
14#include "base/lazy_instance.h"
15#include "base/logging.h"
16#include "base/path_service.h"
17#include "base/process/launch.h"
18#include "base/strings/string_util.h"
19#include "base/strings/utf_string_conversions.h"
20#include "base/synchronization/lock.h"
21#include "base/synchronization/waitable_event.h"
22#include "base/sys_info.h"
23#include "chrome/app/client_util.h"
24#include "chrome/common/automation_messages.h"
25#include "chrome/common/chrome_constants.h"
26#include "chrome/common/chrome_switches.h"
27#include "chrome/test/automation/tab_proxy.h"
28#include "chrome_frame/chrome_launcher_utils.h"
29#include "chrome_frame/crash_reporting/crash_metrics.h"
30#include "chrome_frame/custom_sync_call_context.h"
31#include "chrome_frame/navigation_constraints.h"
32#include "chrome_frame/simple_resource_loader.h"
33#include "chrome_frame/utils.h"
34#include "ui/base/ui_base_switches.h"
35
36namespace {
37
38#ifdef NDEBUG
39int64 kAutomationServerReasonableLaunchDelay = 1000;  // in milliseconds
40#else
41int64 kAutomationServerReasonableLaunchDelay = 1000 * 10;
42#endif
43
44}  // namespace
45
46class ChromeFrameAutomationProxyImpl::TabProxyNotificationMessageFilter
47    : public IPC::ChannelProxy::MessageFilter {
48 public:
49  explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker)
50      : tracker_(tracker) {
51  }
52
53  void AddTabProxy(AutomationHandle tab_proxy) {
54    base::AutoLock lock(lock_);
55    tabs_list_.push_back(tab_proxy);
56  }
57
58  void RemoveTabProxy(AutomationHandle tab_proxy) {
59    base::AutoLock lock(lock_);
60    tabs_list_.remove(tab_proxy);
61  }
62
63  virtual bool OnMessageReceived(const IPC::Message& message) {
64    if (message.is_reply())
65      return false;
66
67    if (!ChromeFrameDelegateImpl::IsTabMessage(message))
68      return false;
69
70    // Get AddRef-ed pointer to corresponding TabProxy object
71    TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(
72        message.routing_id()));
73    bool handled = false;
74    if (tab) {
75      handled = tab->OnMessageReceived(message);
76      tab->Release();
77    } else {
78      DLOG(ERROR) << "Failed to find TabProxy for tab:" << message.routing_id();
79      // To prevent subsequent crashes, we set handled to true in this case.
80      handled = true;
81    }
82    return handled;
83  }
84
85  virtual void OnChannelError() {
86    std::list<AutomationHandle>::const_iterator iter = tabs_list_.begin();
87    for (; iter != tabs_list_.end(); ++iter) {
88      // Get AddRef-ed pointer to corresponding TabProxy object
89      TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(*iter));
90      if (tab) {
91        tab->OnChannelError();
92        tab->Release();
93      }
94    }
95  }
96
97 private:
98  AutomationHandleTracker* tracker_;
99  std::list<AutomationHandle> tabs_list_;
100  base::Lock lock_;
101};
102
103class ChromeFrameAutomationProxyImpl::CFMsgDispatcher
104    : public SyncMessageReplyDispatcher {
105 public:
106  CFMsgDispatcher() : SyncMessageReplyDispatcher() {}
107 protected:
108  virtual bool HandleMessageType(const IPC::Message& msg,
109                                 SyncMessageCallContext* context) {
110    switch (context->message_type()) {
111      case AutomationMsg_CreateExternalTab::ID:
112      case AutomationMsg_ConnectExternalTab::ID:
113        InvokeCallback<CreateExternalTabContext>(msg, context);
114        break;
115      case AutomationMsg_NavigateExternalTabAtIndex::ID:
116      case AutomationMsg_NavigateInExternalTab::ID:
117        InvokeCallback<BeginNavigateContext>(msg, context);
118        break;
119      case AutomationMsg_RunUnloadHandlers::ID:
120        InvokeCallback<UnloadContext>(msg, context);
121        break;
122      default:
123        NOTREACHED();
124    }
125    return true;
126  }
127};
128
129ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl(
130    AutomationProxyCacheEntry* entry,
131    std::string channel_id, base::TimeDelta launch_timeout)
132    : AutomationProxy(launch_timeout, false), proxy_entry_(entry) {
133  TRACE_EVENT_BEGIN_ETW("chromeframe.automationproxy", this, "");
134
135  InitializeChannel(channel_id, false);
136
137  sync_ = new CFMsgDispatcher();
138  message_filter_ = new TabProxyNotificationMessageFilter(tracker_.get());
139
140  // Order of filters is not important.
141  channel_->AddFilter(message_filter_.get());
142  channel_->AddFilter(sync_.get());
143}
144
145ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
146  TRACE_EVENT_END_ETW("chromeframe.automationproxy", this, "");
147}
148
149void ChromeFrameAutomationProxyImpl::SendAsAsync(
150    IPC::SyncMessage* msg,
151    SyncMessageReplyDispatcher::SyncMessageCallContext* context, void* key) {
152  sync_->Push(msg, context, key);
153  channel_->ChannelProxy::Send(msg);
154}
155
156void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) {
157  sync_->Cancel(key);
158}
159
160void ChromeFrameAutomationProxyImpl::OnChannelError() {
161  DLOG(ERROR) << "Automation server died";
162  if (proxy_entry_) {
163    proxy_entry_->OnChannelError();
164  } else {
165    NOTREACHED();
166  }
167}
168
169scoped_refptr<TabProxy> ChromeFrameAutomationProxyImpl::CreateTabProxy(
170    int handle) {
171  DCHECK(tracker_->GetResource(handle) == NULL);
172  TabProxy* tab_proxy = new TabProxy(this, tracker_.get(), handle);
173  if (tab_proxy != NULL)
174    message_filter_->AddTabProxy(handle);
175  return tab_proxy;
176}
177
178void ChromeFrameAutomationProxyImpl::ReleaseTabProxy(AutomationHandle handle) {
179  message_filter_->RemoveTabProxy(handle);
180}
181
182struct LaunchTimeStats {
183#ifndef NDEBUG
184  LaunchTimeStats() {
185    launch_time_begin_ = base::Time::Now();
186  }
187
188  void Dump() {
189    base::TimeDelta launch_time = base::Time::Now() - launch_time_begin_;
190    UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchTime", launch_time);
191    const int64 launch_milliseconds = launch_time.InMilliseconds();
192    if (launch_milliseconds > kAutomationServerReasonableLaunchDelay) {
193      LOG(WARNING) << "Automation server launch took longer than expected: " <<
194          launch_milliseconds << " ms.";
195    }
196  }
197
198  base::Time launch_time_begin_;
199#else
200  void Dump() {}
201#endif
202};
203
204AutomationProxyCacheEntry::AutomationProxyCacheEntry(
205    ChromeFrameLaunchParams* params, LaunchDelegate* delegate)
206    : profile_name(params->profile_name()),
207      launch_result_(AUTOMATION_LAUNCH_RESULT_INVALID) {
208  DCHECK(delegate);
209  thread_.reset(new base::Thread(WideToASCII(profile_name).c_str()));
210  thread_->Start();
211  // Use scoped_refptr so that the params will get released when the task
212  // has been run.
213  scoped_refptr<ChromeFrameLaunchParams> ref_params(params);
214  thread_->message_loop()->PostTask(
215      FROM_HERE, base::Bind(&AutomationProxyCacheEntry::CreateProxy,
216                            base::Unretained(this), ref_params, delegate));
217}
218
219AutomationProxyCacheEntry::~AutomationProxyCacheEntry() {
220  DVLOG(1) << __FUNCTION__ << profile_name;
221  // Attempt to fix chrome_frame_tests crash seen at times on the IE6/IE7
222  // builders. It appears that there are cases when we can enter here when the
223  // AtExitManager is tearing down the global ProxyCache which causes a crash
224  // while tearing down the AutomationProxy object due to a NULL MessageLoop
225  // The AutomationProxy class uses the SyncChannel which assumes the existence
226  // of a MessageLoop instance.
227  // We leak the AutomationProxy pointer here to avoid a crash.
228  if (base::MessageLoop::current() == NULL) {
229    proxy_.release();
230  }
231}
232
233void AutomationProxyCacheEntry::CreateProxy(ChromeFrameLaunchParams* params,
234                                            LaunchDelegate* delegate) {
235  DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
236  DCHECK(delegate);
237  DCHECK(params);
238  DCHECK(proxy_.get() == NULL);
239
240  // We *must* create automationproxy in a thread that has message loop,
241  // since SyncChannel::Context construction registers event to be watched
242  // through ObjectWatcher which subscribes for the current thread message loop
243  // destruction notification.
244
245  // At same time we must destroy/stop the thread from another thread.
246  std::string channel_id = AutomationProxy::GenerateChannelID();
247  ChromeFrameAutomationProxyImpl* proxy =
248      new ChromeFrameAutomationProxyImpl(
249          this,
250          channel_id,
251          base::TimeDelta::FromMilliseconds(params->launch_timeout()));
252
253  // Ensure that the automation proxy actually respects our choice on whether
254  // or not to check the version.
255  proxy->set_perform_version_check(params->version_check());
256
257  // Launch browser
258  std::wstring command_line_string;
259  scoped_ptr<CommandLine> command_line;
260  if (chrome_launcher::CreateLaunchCommandLine(&command_line)) {
261    command_line->AppendSwitchASCII(switches::kAutomationClientChannelID,
262                                    channel_id);
263
264    // Run Chrome in Chrome Frame mode. In practice, this modifies the paths
265    // and registry keys that Chrome looks in via the BrowserDistribution
266    // mechanism.
267    command_line->AppendSwitch(switches::kChromeFrame);
268
269    // Chrome Frame never wants Chrome to start up with a First Run UI.
270    command_line->AppendSwitch(switches::kNoFirstRun);
271
272    // Chrome Frame never wants to run background extensions since they
273    // interfere with in-use updates.
274    command_line->AppendSwitch(switches::kDisableBackgroundMode);
275
276    command_line->AppendSwitch(switches::kDisablePopupBlocking);
277
278#if defined(GOOGLE_CHROME_BUILD)
279    // Chrome Frame should use the native print dialog.
280    command_line->AppendSwitch(switches::kDisablePrintPreview);
281#endif
282
283    // Disable the "Whoa! Chrome has crashed." dialog, because that isn't very
284    // useful for Chrome Frame users.
285#ifndef NDEBUG
286    command_line->AppendSwitch(switches::kNoErrorDialogs);
287#endif
288
289    // In headless mode runs like reliability test runs we want full crash dumps
290    // from chrome.
291    if (IsHeadlessMode())
292      command_line->AppendSwitch(switches::kFullMemoryCrashReport);
293
294    // In accessible mode automation tests expect renderer accessibility to be
295    // enabled in chrome.
296    if (IsAccessibleMode())
297      command_line->AppendSwitch(switches::kForceRendererAccessibility);
298
299    DVLOG(1) << "Profile path: " << params->profile_path().value();
300    command_line->AppendSwitchPath(switches::kUserDataDir,
301                                   params->profile_path());
302
303    // Ensure that Chrome is running the specified version of chrome.dll.
304    command_line->AppendSwitchNative(switches::kChromeVersion,
305                                     GetCurrentModuleVersion());
306
307    if (!params->language().empty())
308      command_line->AppendSwitchNative(switches::kLang, params->language());
309
310    command_line_string = command_line->GetCommandLineString();
311  }
312
313  automation_server_launch_start_time_ = base::TimeTicks::Now();
314
315  if (command_line_string.empty() ||
316      !base::LaunchProcess(command_line_string, base::LaunchOptions(), NULL)) {
317    // We have no code for launch failure.
318    launch_result_ = AUTOMATION_LAUNCH_RESULT_INVALID;
319  } else {
320    // Launch timeout may happen if the new instance tries to communicate
321    // with an existing Chrome instance that is hung and displays msgbox
322    // asking to kill the previous one. This could be easily observed if the
323    // already running Chrome instance is running as high-integrity process
324    // (started with "Run as Administrator" or launched by another high
325    // integrity process) hence our medium-integrity process
326    // cannot SendMessage to it with request to activate itself.
327
328    // TODO(stoyan) AutomationProxy eats Hello message, hence installing
329    // message filter is pointless, we can leverage ObjectWatcher and use
330    // system thread pool to notify us when proxy->AppLaunch event is signaled.
331    LaunchTimeStats launch_stats;
332    // Wait for the automation server launch result, then stash away the
333    // version string it reported.
334    launch_result_ = proxy->WaitForAppLaunch();
335    launch_stats.Dump();
336
337    base::TimeDelta delta =
338        base::TimeTicks::Now() - automation_server_launch_start_time_;
339
340    if (launch_result_ == AUTOMATION_SUCCESS) {
341      UMA_HISTOGRAM_TIMES(
342          "ChromeFrame.AutomationServerLaunchSuccessTime", delta);
343    } else {
344      UMA_HISTOGRAM_TIMES(
345          "ChromeFrame.AutomationServerLaunchFailedTime", delta);
346    }
347
348    UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.LaunchResult",
349                                launch_result_,
350                                AUTOMATION_SUCCESS,
351                                AUTOMATION_CREATE_TAB_FAILED,
352                                AUTOMATION_CREATE_TAB_FAILED + 1);
353  }
354
355  TRACE_EVENT_END_ETW("chromeframe.createproxy", this, "");
356
357  // Finally set the proxy.
358  proxy_.reset(proxy);
359  launch_delegates_.push_back(delegate);
360
361  delegate->LaunchComplete(proxy_.get(), launch_result_);
362}
363
364void AutomationProxyCacheEntry::RemoveDelegate(LaunchDelegate* delegate,
365                                               base::WaitableEvent* done,
366                                               bool* was_last_delegate) {
367  DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
368  DCHECK(delegate);
369  DCHECK(done);
370  DCHECK(was_last_delegate);
371
372  *was_last_delegate = false;
373
374  LaunchDelegates::iterator it = std::find(launch_delegates_.begin(),
375      launch_delegates_.end(), delegate);
376  if (it == launch_delegates_.end()) {
377    NOTREACHED();
378  } else {
379    if (launch_delegates_.size() == 1) {
380      *was_last_delegate = true;
381
382      // Process pending notifications.
383      thread_->message_loop()->RunUntilIdle();
384
385      // Take down the proxy since we no longer have any clients.
386      // Make sure we only do this once all pending messages have been cleared.
387      proxy_.reset(NULL);
388    }
389    // Be careful to remove from the list after running pending
390    // tasks.  Otherwise the delegate being removed might miss out
391    // on pending notifications such as LaunchComplete.
392    launch_delegates_.erase(it);
393  }
394
395  done->Signal();
396}
397
398void AutomationProxyCacheEntry::AddDelegate(LaunchDelegate* delegate) {
399  DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
400  DCHECK(std::find(launch_delegates_.begin(),
401                   launch_delegates_.end(),
402                   delegate) == launch_delegates_.end())
403      << "Same delegate being added twice";
404  DCHECK(launch_result_ != AUTOMATION_LAUNCH_RESULT_INVALID);
405
406  launch_delegates_.push_back(delegate);
407  delegate->LaunchComplete(proxy_.get(), launch_result_);
408}
409
410void AutomationProxyCacheEntry::OnChannelError() {
411  DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
412  launch_result_ = AUTOMATION_SERVER_CRASHED;
413  LaunchDelegates::const_iterator it = launch_delegates_.begin();
414  for (; it != launch_delegates_.end(); ++it) {
415    (*it)->AutomationServerDied();
416  }
417}
418
419ProxyFactory::ProxyFactory() {
420}
421
422ProxyFactory::~ProxyFactory() {
423  for (size_t i = 0; i < proxies_.container().size(); ++i) {
424    DWORD result = proxies_[i]->WaitForThread(0);
425    if (WAIT_OBJECT_0 != result)
426      // TODO(stoyan): Don't leak proxies on exit.
427      DLOG(ERROR) << "Proxies leaked on exit.";
428  }
429}
430
431void ProxyFactory::GetAutomationServer(
432    LaunchDelegate* delegate, ChromeFrameLaunchParams* params,
433    void** automation_server_id) {
434  TRACE_EVENT_BEGIN_ETW("chromeframe.createproxy", this, "");
435
436  scoped_refptr<AutomationProxyCacheEntry> entry;
437  // Find already existing launcher thread for given profile
438  base::AutoLock lock(lock_);
439  for (size_t i = 0; i < proxies_.container().size(); ++i) {
440    if (proxies_[i]->IsSameProfile(params->profile_name())) {
441      entry = proxies_[i];
442      break;
443    }
444  }
445
446  if (entry == NULL) {
447    DVLOG(1) << __FUNCTION__ << " creating new proxy entry";
448    entry = new AutomationProxyCacheEntry(params, delegate);
449    proxies_.container().push_back(entry);
450  } else if (delegate) {
451    // Notify the new delegate of the launch status from the worker thread
452    // and add it to the list of delegates.
453    entry->message_loop()->PostTask(
454        FROM_HERE, base::Bind(&AutomationProxyCacheEntry::AddDelegate,
455                              base::Unretained(entry.get()), delegate));
456  }
457
458  DCHECK(automation_server_id != NULL);
459  DCHECK(!entry->IsSameThread(base::PlatformThread::CurrentId()));
460
461  *automation_server_id = entry;
462}
463
464bool ProxyFactory::ReleaseAutomationServer(void* server_id,
465                                           LaunchDelegate* delegate) {
466  if (!server_id) {
467    NOTREACHED();
468    return false;
469  }
470
471  AutomationProxyCacheEntry* entry =
472      reinterpret_cast<AutomationProxyCacheEntry*>(server_id);
473
474#ifndef NDEBUG
475  lock_.Acquire();
476  Vector::ContainerType::iterator it = std::find(proxies_.container().begin(),
477                                                 proxies_.container().end(),
478                                                 entry);
479  DCHECK(it != proxies_.container().end());
480  DCHECK(!entry->IsSameThread(base::PlatformThread::CurrentId()));
481
482  lock_.Release();
483#endif
484
485  // AddRef the entry object as we might need to take it out of the proxy
486  // stack and then uninitialize the entry.
487  entry->AddRef();
488
489  bool last_delegate = false;
490  if (delegate) {
491    base::WaitableEvent done(true, false);
492    entry->message_loop()->PostTask(
493        FROM_HERE,
494        base::Bind(&AutomationProxyCacheEntry::RemoveDelegate,
495                   base::Unretained(entry), delegate, &done, &last_delegate));
496    done.Wait();
497  }
498
499  if (last_delegate) {
500    lock_.Acquire();
501    Vector::ContainerType::iterator it = std::find(proxies_.container().begin(),
502                                                   proxies_.container().end(),
503                                                   entry);
504    if (it != proxies_.container().end()) {
505      proxies_.container().erase(it);
506    } else {
507      DLOG(ERROR) << "Proxy wasn't found. Proxy map is likely empty (size="
508                  << proxies_.container().size() << ").";
509    }
510
511    lock_.Release();
512  }
513
514  entry->Release();
515
516  return true;
517}
518
519static base::LazyInstance<ProxyFactory>::Leaky
520    g_proxy_factory = LAZY_INSTANCE_INITIALIZER;
521
522ChromeFrameAutomationClient::ChromeFrameAutomationClient()
523    : chrome_frame_delegate_(NULL),
524      chrome_window_(NULL),
525      tab_window_(NULL),
526      parent_window_(NULL),
527      automation_server_(NULL),
528      automation_server_id_(NULL),
529      ui_thread_id_(NULL),
530      init_state_(UNINITIALIZED),
531      use_chrome_network_(false),
532      proxy_factory_(g_proxy_factory.Pointer()),
533      handle_top_level_requests_(false),
534      tab_handle_(-1),
535      session_id_(-1),
536      external_tab_cookie_(0),
537      url_fetcher_(NULL),
538      url_fetcher_flags_(PluginUrlRequestManager::NOT_THREADSAFE),
539      navigate_after_initialization_(false),
540      route_all_top_level_navigations_(false) {
541}
542
543ChromeFrameAutomationClient::~ChromeFrameAutomationClient() {
544  // Uninitialize must be called prior to the destructor
545  DCHECK(automation_server_ == NULL);
546}
547
548bool ChromeFrameAutomationClient::Initialize(
549    ChromeFrameDelegate* chrome_frame_delegate,
550    ChromeFrameLaunchParams* chrome_launch_params) {
551  DCHECK(!IsWindow());
552  chrome_frame_delegate_ = chrome_frame_delegate;
553
554#ifndef NDEBUG
555  if (chrome_launch_params_ && chrome_launch_params_ != chrome_launch_params) {
556    DCHECK_EQ(chrome_launch_params_->url(), chrome_launch_params->url());
557    DCHECK_EQ(chrome_launch_params_->referrer(),
558              chrome_launch_params->referrer());
559  }
560#endif
561
562  chrome_launch_params_ = chrome_launch_params;
563
564  ui_thread_id_ = base::PlatformThread::CurrentId();
565#ifndef NDEBUG
566  // In debug mode give more time to work with a debugger.
567  if (IsDebuggerPresent()) {
568    // Don't use INFINITE (which is -1) or even MAXINT since we will convert
569    // from milliseconds to microseconds when stored in a base::TimeDelta,
570    // thus * 1000. An hour should be enough.
571    chrome_launch_params_->set_launch_timeout(60 * 60 * 1000);
572  } else {
573    DCHECK_LT(chrome_launch_params_->launch_timeout(),
574              MAXINT / 2000);
575    chrome_launch_params_->set_launch_timeout(
576        chrome_launch_params_->launch_timeout() * 2);
577  }
578#endif  // NDEBUG
579
580  // Create a window on the UI thread for marshaling messages back and forth
581  // from the IPC thread. This window cannot be a message only window as the
582  // external chrome tab window is created as a child of this window. This
583  // window is eventually reparented to the ActiveX plugin window.
584  if (!Create(GetDesktopWindow(), NULL, NULL,
585              WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
586              WS_EX_TOOLWINDOW)) {
587    NOTREACHED();
588    return false;
589  }
590
591  // Keep object in memory, while the window is alive.
592  // Corresponding Release is in OnFinalMessage();
593  AddRef();
594
595  // Mark our state as initializing.  We'll reach initialized once
596  // InitializeComplete is called successfully.
597  init_state_ = INITIALIZING;
598
599  HRESULT hr = S_OK;
600
601  if (chrome_launch_params_->url().is_valid())
602    navigate_after_initialization_ = false;
603
604  proxy_factory_->GetAutomationServer(static_cast<LaunchDelegate*>(this),
605      chrome_launch_params_, &automation_server_id_);
606
607  return true;
608}
609
610void ChromeFrameAutomationClient::Uninitialize() {
611  if (init_state_ == UNINITIALIZED) {
612    DLOG(WARNING) << __FUNCTION__ << ": Automation client not initialized";
613    return;
614  }
615
616  init_state_ = UNINITIALIZING;
617
618  // Called from client's FinalRelease() / destructor
619  if (url_fetcher_) {
620    // Clean up any outstanding requests
621    url_fetcher_->StopAllRequests();
622    url_fetcher_ = NULL;
623  }
624
625  if (tab_) {
626    tab_->RemoveObserver(this);
627    if (automation_server_)
628      automation_server_->ReleaseTabProxy(tab_->handle());
629    tab_ = NULL;    // scoped_refptr::Release
630  }
631
632  // Wait for the automation proxy's worker thread to exit.
633  ReleaseAutomationServer();
634
635  // We must destroy the window, since if there are pending tasks
636  // window procedure may be invoked after DLL is unloaded.
637  // Unfortunately pending tasks are leaked.
638  if (::IsWindow(m_hWnd))
639    DestroyWindow();
640
641  // DCHECK(navigate_after_initialization_ == false);
642  handle_top_level_requests_ = false;
643  ui_thread_id_ = 0;
644  chrome_frame_delegate_ = NULL;
645  init_state_ = UNINITIALIZED;
646}
647
648bool ChromeFrameAutomationClient::InitiateNavigation(
649    const std::string& url,
650    const std::string& referrer,
651    NavigationConstraints* navigation_constraints) {
652  if (url.empty())
653    return false;
654
655  GURL parsed_url(url);
656
657  // Catch invalid URLs early.
658  // Can we allow this navigation to happen?
659  if (!CanNavigate(parsed_url, navigation_constraints)) {
660    DLOG(ERROR) << __FUNCTION__ << " Not allowing navigation to: " << url;
661    return false;
662  }
663
664  // If we are not yet initialized ignore attempts to navigate to the same url.
665  // Navigation attempts to the same URL could occur if the automation client
666  // was reused for a new active document instance.
667  if (!chrome_launch_params_ || is_initialized() ||
668      parsed_url != chrome_launch_params_->url()) {
669    // Important: Since we will be using the referrer_ variable from a
670    // different thread, we need to force a new std::string buffer instance for
671    // the referrer_ GURL variable.  Otherwise we can run into strangeness when
672    // the GURL is accessed and it could result in a bad URL that can cause the
673    // referrer to be dropped or something worse.
674    GURL referrer_gurl(referrer.c_str());
675    if (!chrome_launch_params_) {
676      base::FilePath profile_path;
677      chrome_launch_params_ = new ChromeFrameLaunchParams(parsed_url,
678          referrer_gurl, profile_path, L"", SimpleResourceLoader::GetLanguage(),
679          false, false, route_all_top_level_navigations_);
680    } else {
681      chrome_launch_params_->set_referrer(referrer_gurl);
682      chrome_launch_params_->set_url(parsed_url);
683    }
684
685    navigate_after_initialization_ = false;
686
687    if (is_initialized()) {
688      BeginNavigate();
689    } else {
690      navigate_after_initialization_ = true;
691    }
692  }
693
694  return true;
695}
696
697bool ChromeFrameAutomationClient::NavigateToIndex(int index) {
698  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
699  if (!automation_server_ || !tab_.get() || !tab_->is_valid()) {
700    return false;
701  }
702
703  DCHECK(::IsWindow(chrome_window_));
704
705  IPC::SyncMessage* msg = new AutomationMsg_NavigateExternalTabAtIndex(
706      tab_->handle(), index, NULL);
707  automation_server_->SendAsAsync(msg, new BeginNavigateContext(this),
708                                  this);
709  return true;
710}
711
712bool ChromeFrameAutomationClient::ForwardMessageFromExternalHost(
713    const std::string& message, const std::string& origin,
714    const std::string& target) {
715  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
716  if (!is_initialized())
717    return false;
718
719  tab_->HandleMessageFromExternalHost(message, origin, target);
720  return true;
721}
722
723bool ChromeFrameAutomationClient::SetProxySettings(
724    const std::string& json_encoded_proxy_settings) {
725  if (!is_initialized())
726    return false;
727  automation_server_->SendProxyConfig(json_encoded_proxy_settings);
728  return true;
729}
730
731void ChromeFrameAutomationClient::BeginNavigate() {
732  // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
733  if (!automation_server_ || !tab_.get()) {
734    DLOG(WARNING) << "BeginNavigate - can't navigate.";
735    ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR,
736                          chrome_launch_params_->url().spec());
737    return;
738  }
739
740  DCHECK(::IsWindow(chrome_window_));
741
742  if (!tab_->is_valid()) {
743    DLOG(WARNING) << "BeginNavigate - tab isn't valid.";
744    return;
745  }
746
747  IPC::SyncMessage* msg =
748      new AutomationMsg_NavigateInExternalTab(tab_->handle(),
749          chrome_launch_params_->url(), chrome_launch_params_->referrer(),
750          NULL);
751  automation_server_->SendAsAsync(msg, new BeginNavigateContext(this), this);
752
753  RECT client_rect = {0};
754  chrome_frame_delegate_->GetBounds(&client_rect);
755  Resize(client_rect.right - client_rect.left,
756         client_rect.bottom - client_rect.top,
757         SWP_NOACTIVATE | SWP_NOZORDER);
758}
759
760void ChromeFrameAutomationClient::BeginNavigateCompleted(
761    AutomationMsg_NavigationResponseValues result) {
762  if (result == AUTOMATION_MSG_NAVIGATION_ERROR)
763     ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR,
764                           chrome_launch_params_->url().spec());
765}
766
767void ChromeFrameAutomationClient::FindInPage(const std::wstring& search_string,
768                                             FindInPageDirection forward,
769                                             FindInPageCase match_case,
770                                             bool find_next) {
771  // Note that we can be called by the find dialog after the tab has gone away.
772  if (!tab_)
773    return;
774
775  // What follows is quite similar to TabProxy::FindInPage() but uses
776  // the SyncMessageReplyDispatcher to avoid concerns about blocking
777  // synchronous messages.
778  AutomationMsg_Find_Params params;
779  params.search_string = WideToUTF16Hack(search_string);
780  params.find_next = find_next;
781  params.match_case = (match_case == CASE_SENSITIVE);
782  params.forward = (forward == FWD);
783
784  IPC::SyncMessage* msg =
785      new AutomationMsg_Find(tab_->handle(), params, NULL, NULL);
786  automation_server_->SendAsAsync(msg, NULL, this);
787}
788
789void ChromeFrameAutomationClient::OnChromeFrameHostMoved() {
790  // Use a local var to avoid the small possibility of getting the tab_
791  // member be cleared while we try to use it.
792  // Note that TabProxy is a RefCountedThreadSafe object, so we should be OK.
793  scoped_refptr<TabProxy> tab(tab_);
794  // There also is a possibility that tab_ has not been set yet,
795  // so we still need to test for NULL.
796  if (tab)
797    tab->OnHostMoved();
798}
799
800void ChromeFrameAutomationClient::CreateExternalTab() {
801  AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
802  DCHECK(IsWindow());
803  DCHECK(automation_server_ != NULL);
804
805  if (chrome_launch_params_->url().is_valid()) {
806    navigate_after_initialization_ = false;
807  }
808
809  ExternalTabSettings settings;
810  settings.parent = m_hWnd;
811  settings.style = WS_CHILD;
812  settings.is_incognito = chrome_launch_params_->incognito();
813  settings.load_requests_via_automation = !use_chrome_network_;
814  settings.handle_top_level_requests = handle_top_level_requests_;
815  settings.initial_url = chrome_launch_params_->url();
816  settings.referrer = chrome_launch_params_->referrer();
817  // Infobars disabled in widget mode.
818  settings.infobars_enabled = !chrome_launch_params_->widget_mode();
819  settings.route_all_top_level_navigations =
820      chrome_launch_params_->route_all_top_level_navigations();
821
822  UMA_HISTOGRAM_CUSTOM_COUNTS(
823      "ChromeFrame.HostNetworking", !use_chrome_network_, 1, 2, 3);
824
825  UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.HandleTopLevelRequests",
826                              handle_top_level_requests_, 1, 2, 3);
827
828  IPC::SyncMessage* message =
829      new AutomationMsg_CreateExternalTab(settings, NULL, NULL, 0, 0);
830  automation_server_->SendAsAsync(message, new CreateExternalTabContext(this),
831                                  this);
832}
833
834AutomationLaunchResult ChromeFrameAutomationClient::CreateExternalTabComplete(
835    HWND chrome_window, HWND tab_window, int tab_handle, int session_id) {
836  if (!automation_server_) {
837    // If we receive this notification while shutting down, do nothing.
838    DLOG(ERROR) << "CreateExternalTabComplete called when automation server "
839                << "was null!";
840    return AUTOMATION_CREATE_TAB_FAILED;
841  }
842
843  AutomationLaunchResult launch_result = AUTOMATION_SUCCESS;
844  if (tab_handle == 0 || !::IsWindow(chrome_window)) {
845    launch_result = AUTOMATION_CREATE_TAB_FAILED;
846  } else {
847    chrome_window_ = chrome_window;
848    tab_window_ = tab_window;
849    tab_ = automation_server_->CreateTabProxy(tab_handle);
850    tab_->AddObserver(this);
851    tab_handle_ = tab_handle;
852    session_id_ = session_id;
853  }
854  return launch_result;
855}
856
857// Invoked in the automation proxy's worker thread.
858void ChromeFrameAutomationClient::LaunchComplete(
859    ChromeFrameAutomationProxy* proxy,
860    AutomationLaunchResult result) {
861  // If we're shutting down we don't keep a pointer to the automation server.
862  if (init_state_ != UNINITIALIZING) {
863    DCHECK(init_state_ == INITIALIZING);
864    automation_server_ = proxy;
865  } else {
866    DVLOG(1) << "Not storing automation server pointer due to shutting down";
867  }
868
869  if (result == AUTOMATION_SUCCESS) {
870    // NOTE: A potential problem here is that Uninitialize() may just have
871    // been called so we need to be careful and check the automation_server_
872    // pointer.
873    if (automation_server_ != NULL) {
874      // If we have a valid tab_handle here it means that we are attaching to
875      // an existing ExternalTabContainer instance, in which case we don't
876      // want to create an external tab instance in Chrome.
877      if (external_tab_cookie_ == 0) {
878        // Continue with Initialization - Create external tab
879        CreateExternalTab();
880      } else {
881        // Send a notification to Chrome that we are ready to connect to the
882        // ExternalTab.
883        IPC::SyncMessage* message =
884            new AutomationMsg_ConnectExternalTab(external_tab_cookie_, true,
885              m_hWnd, NULL, NULL, NULL, 0);
886        automation_server_->SendAsAsync(message,
887                                        new CreateExternalTabContext(this),
888                                        this);
889        DVLOG(1) << __FUNCTION__ << ": sending CreateExternalTabComplete";
890      }
891    }
892  } else {
893    // Launch failed. Note, we cannot delete proxy here.
894    PostTask(FROM_HERE,
895             base::Bind(&ChromeFrameAutomationClient::InitializeComplete,
896                        base::Unretained(this), result));
897  }
898}
899
900// Invoked in the automation proxy's worker thread.
901void ChromeFrameAutomationClient::AutomationServerDied() {
902  // Make sure we notify our delegate.
903  PostTask(
904      FROM_HERE, base::Bind(&ChromeFrameAutomationClient::InitializeComplete,
905                            base::Unretained(this), AUTOMATION_SERVER_CRASHED));
906  // Then uninitialize.
907  PostTask(
908      FROM_HERE, base::Bind(&ChromeFrameAutomationClient::Uninitialize,
909                            base::Unretained(this)));
910}
911
912void ChromeFrameAutomationClient::InitializeComplete(
913    AutomationLaunchResult result) {
914  DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_);
915  if (result != AUTOMATION_SUCCESS) {
916    DLOG(WARNING) << "InitializeComplete: failure " << result;
917  } else {
918    init_state_ = INITIALIZED;
919
920    // If the host already have a window, ask Chrome to re-parent.
921    if (parent_window_)
922      SetParentWindow(parent_window_);
923
924    // If host specified destination URL - navigate. Apparently we do not use
925    // accelerator table.
926    if (navigate_after_initialization_) {
927      navigate_after_initialization_ = false;
928      BeginNavigate();
929    }
930  }
931
932  if (chrome_frame_delegate_) {
933    if (result == AUTOMATION_SUCCESS) {
934      chrome_frame_delegate_->OnAutomationServerReady();
935    } else {
936      std::string version;
937      if (automation_server_)
938        version = automation_server_->server_version();
939      chrome_frame_delegate_->OnAutomationServerLaunchFailed(result, version);
940    }
941  }
942}
943
944bool ChromeFrameAutomationClient::ProcessUrlRequestMessage(TabProxy* tab,
945    const IPC::Message& msg, bool ui_thread) {
946  // Either directly call appropriate url_fetcher function
947  // or postpone call to the UI thread.
948  uint16 msg_type = msg.type();
949  switch (msg_type) {
950    default:
951      return false;
952
953    case AutomationMsg_RequestStart::ID:
954      if (ui_thread || (url_fetcher_flags_ &
955                           PluginUrlRequestManager::START_REQUEST_THREADSAFE)) {
956        AutomationMsg_RequestStart::Dispatch(&msg, url_fetcher_, this,
957            &PluginUrlRequestManager::StartUrlRequest);
958        return true;
959      }
960      break;
961
962    case AutomationMsg_RequestRead::ID:
963      if (ui_thread || (url_fetcher_flags_ &
964                            PluginUrlRequestManager::READ_REQUEST_THREADSAFE)) {
965        AutomationMsg_RequestRead::Dispatch(&msg, url_fetcher_, this,
966            &PluginUrlRequestManager::ReadUrlRequest);
967        return true;
968      }
969      break;
970
971    case AutomationMsg_RequestEnd::ID:
972      if (ui_thread || (url_fetcher_flags_ &
973                            PluginUrlRequestManager::STOP_REQUEST_THREADSAFE)) {
974        AutomationMsg_RequestEnd::Dispatch(&msg, url_fetcher_, this,
975            &PluginUrlRequestManager::EndUrlRequest);
976        return true;
977      }
978      break;
979
980    case AutomationMsg_DownloadRequestInHost::ID:
981      if (ui_thread || (url_fetcher_flags_ &
982                        PluginUrlRequestManager::DOWNLOAD_REQUEST_THREADSAFE)) {
983        AutomationMsg_DownloadRequestInHost::Dispatch(&msg, url_fetcher_, this,
984            &PluginUrlRequestManager::DownloadUrlRequestInHost);
985        return true;
986      }
987      break;
988  }
989
990  PostTask(
991      FROM_HERE,
992      base::Bind(
993          base::IgnoreResult(
994              &ChromeFrameAutomationClient::ProcessUrlRequestMessage),
995          base::Unretained(this), tab, msg, true));
996  return true;
997}
998
999// These are invoked in channel's background thread.
1000// Cannot call any method of the activex here since it is a STA kind of being.
1001// By default we marshal the IPC message to the main/GUI thread and from there
1002// we safely invoke chrome_frame_delegate_->OnMessageReceived(msg).
1003bool ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab,
1004                                                    const IPC::Message& msg) {
1005  DCHECK(tab == tab_.get());
1006  // Quickly process network related messages.
1007  if (url_fetcher_ && ProcessUrlRequestMessage(tab, msg, false))
1008    return true;
1009
1010  // Early check to avoid needless marshaling
1011  if (chrome_frame_delegate_ == NULL)
1012    return false;
1013
1014  PostTask(FROM_HERE,
1015           base::Bind(&ChromeFrameAutomationClient::OnMessageReceivedUIThread,
1016                      base::Unretained(this), msg));
1017  return true;
1018}
1019
1020void ChromeFrameAutomationClient::OnChannelError(TabProxy* tab) {
1021  DCHECK(tab == tab_.get());
1022  // Early check to avoid needless marshaling
1023  if (chrome_frame_delegate_ == NULL)
1024    return;
1025
1026  PostTask(
1027      FROM_HERE,
1028      base::Bind(&ChromeFrameAutomationClient::OnChannelErrorUIThread,
1029                 base::Unretained(this)));
1030}
1031
1032void ChromeFrameAutomationClient::OnMessageReceivedUIThread(
1033    const IPC::Message& msg) {
1034  DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_);
1035  // Forward to the delegate.
1036  if (chrome_frame_delegate_)
1037    chrome_frame_delegate_->OnMessageReceived(msg);
1038}
1039
1040void ChromeFrameAutomationClient::OnChannelErrorUIThread() {
1041  DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_);
1042
1043  // Report a metric that something went wrong unexpectedly.
1044  CrashMetricsReporter::GetInstance()->IncrementMetric(
1045      CrashMetricsReporter::CHANNEL_ERROR_COUNT);
1046
1047  // Forward to the delegate.
1048  if (chrome_frame_delegate_)
1049    chrome_frame_delegate_->OnChannelError();
1050}
1051
1052void ChromeFrameAutomationClient::ReportNavigationError(
1053    AutomationMsg_NavigationResponseValues error_code,
1054    const std::string& url) {
1055  if (!chrome_frame_delegate_)
1056    return;
1057
1058  if (ui_thread_id_ == base::PlatformThread::CurrentId()) {
1059    chrome_frame_delegate_->OnLoadFailed(error_code, url);
1060  } else {
1061    PostTask(FROM_HERE,
1062             base::Bind(&ChromeFrameAutomationClient::ReportNavigationError,
1063                        base::Unretained(this), error_code, url));
1064  }
1065}
1066
1067void ChromeFrameAutomationClient::Resize(int width, int height,
1068                                         int flags) {
1069  if (tab_.get() && ::IsWindow(chrome_window())) {
1070    SetWindowPos(HWND_TOP, 0, 0, width, height, flags);
1071    tab_->Reposition(chrome_window(), HWND_TOP, 0, 0, width, height,
1072                     flags, m_hWnd);
1073  }
1074}
1075
1076void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window) {
1077  parent_window_ = parent_window;
1078  // If we're done with the initialization step, go ahead
1079  if (is_initialized()) {
1080    if (parent_window == NULL) {
1081      // Hide and reparent the automation window. This window will get
1082      // reparented to the new ActiveX/Active document window when it gets
1083      // created.
1084      ShowWindow(SW_HIDE);
1085      SetParent(GetDesktopWindow());
1086    } else {
1087      if (!::IsWindow(chrome_window())) {
1088        DLOG(WARNING) << "Invalid Chrome Window handle in SetParentWindow";
1089        return;
1090      }
1091
1092      if (!SetParent(parent_window)) {
1093        DLOG(WARNING) << "Failed to set parent window for automation window. "
1094                      << "Error = "
1095                      << GetLastError();
1096        return;
1097      }
1098
1099      RECT parent_client_rect = {0};
1100      ::GetClientRect(parent_window, &parent_client_rect);
1101      int width = parent_client_rect.right - parent_client_rect.left;
1102      int height = parent_client_rect.bottom - parent_client_rect.top;
1103
1104      Resize(width, height, SWP_SHOWWINDOW | SWP_NOZORDER);
1105    }
1106  }
1107}
1108
1109void ChromeFrameAutomationClient::ReleaseAutomationServer() {
1110  if (automation_server_id_) {
1111    // Cache the server id and clear the automation_server_id_ before
1112    // calling ReleaseAutomationServer.  The reason we do this is that
1113    // we must cancel pending messages before we release the automation server.
1114    // Furthermore, while ReleaseAutomationServer is running, we could get
1115    // a callback to LaunchComplete which could cause an external tab to be
1116    // created. Ideally the callbacks should be dropped.
1117    // TODO(ananta)
1118    // Refactor the ChromeFrameAutomationProxy code to not depend on
1119    // AutomationProxy and simplify the whole mess.
1120    void* server_id = automation_server_id_;
1121    automation_server_id_ = NULL;
1122
1123    if (automation_server_) {
1124      // Make sure to clean up any pending sync messages before we go away.
1125      automation_server_->CancelAsync(this);
1126    }
1127
1128    proxy_factory_->ReleaseAutomationServer(server_id, this);
1129    automation_server_ = NULL;
1130
1131    // automation_server_ must not have been set to non NULL.
1132    // (if this regresses, start by looking at LaunchComplete()).
1133    DCHECK(automation_server_ == NULL);
1134  } else {
1135    DCHECK(automation_server_ == NULL);
1136  }
1137}
1138
1139void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
1140  int selected_command) {
1141  if (tab_)
1142    tab_->SendContextMenuCommand(selected_command);
1143}
1144
1145std::wstring ChromeFrameAutomationClient::GetVersion() const {
1146  return GetCurrentModuleVersion();
1147}
1148
1149void ChromeFrameAutomationClient::Print(HDC print_dc,
1150                                        const RECT& print_bounds) {
1151  if (!tab_window_) {
1152    NOTREACHED();
1153    return;
1154  }
1155
1156  HDC window_dc = ::GetDC(tab_window_);
1157
1158  BitBlt(print_dc, print_bounds.left, print_bounds.top,
1159         print_bounds.right - print_bounds.left,
1160         print_bounds.bottom - print_bounds.top,
1161         window_dc, print_bounds.left, print_bounds.top,
1162         SRCCOPY);
1163
1164  ::ReleaseDC(tab_window_, window_dc);
1165}
1166
1167void ChromeFrameAutomationClient::PrintTab() {
1168  if (tab_)
1169    tab_->PrintAsync();
1170}
1171
1172void ChromeFrameAutomationClient::AttachExternalTab(
1173    uint64 external_tab_cookie) {
1174  DCHECK_EQ(static_cast<TabProxy*>(NULL), tab_.get());
1175  DCHECK_EQ(-1, tab_handle_);
1176
1177  external_tab_cookie_ = external_tab_cookie;
1178}
1179
1180void ChromeFrameAutomationClient::BlockExternalTab(uint64 cookie) {
1181  // The host does not want this tab to be shown (due popup blocker).
1182  IPC::SyncMessage* message =
1183      new AutomationMsg_ConnectExternalTab(cookie, false, m_hWnd,
1184                                           NULL, NULL, NULL, 0);
1185  automation_server_->SendAsAsync(message, NULL, this);
1186}
1187
1188void ChromeFrameAutomationClient::SetPageFontSize(
1189    enum AutomationPageFontSize font_size) {
1190  if (font_size < SMALLEST_FONT ||
1191      font_size > LARGEST_FONT) {
1192      NOTREACHED() << "Invalid font size specified : "
1193                   << font_size;
1194      return;
1195  }
1196
1197  automation_server_->Send(
1198      new AutomationMsg_SetPageFontSize(tab_handle_, font_size));
1199}
1200
1201void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask) {
1202  automation_server_->Send(new AutomationMsg_RemoveBrowsingData(remove_mask));
1203}
1204
1205void ChromeFrameAutomationClient::SetUrlFetcher(
1206    PluginUrlRequestManager* url_fetcher) {
1207  DCHECK(url_fetcher != NULL);
1208  url_fetcher_ = url_fetcher;
1209  url_fetcher_flags_ = url_fetcher->GetThreadSafeFlags();
1210  url_fetcher_->set_delegate(this);
1211}
1212
1213void ChromeFrameAutomationClient::SetZoomLevel(content::PageZoom zoom_level) {
1214  if (automation_server_) {
1215    automation_server_->Send(new AutomationMsg_SetZoomLevel(tab_handle_,
1216                                                            zoom_level));
1217  }
1218}
1219
1220void ChromeFrameAutomationClient::OnUnload(bool* should_unload) {
1221  *should_unload = true;
1222  if (automation_server_) {
1223    const DWORD kUnloadEventTimeout = 20000;
1224
1225    IPC::SyncMessage* msg = new AutomationMsg_RunUnloadHandlers(tab_handle_,
1226                                                                should_unload);
1227    base::WaitableEvent unload_call_finished(false, false);
1228    UnloadContext* unload_context = new UnloadContext(&unload_call_finished,
1229                                                      should_unload);
1230    automation_server_->SendAsAsync(msg, unload_context, this);
1231    HANDLE done = unload_call_finished.handle();
1232    WaitWithMessageLoop(&done, 1, kUnloadEventTimeout);
1233  }
1234}
1235
1236//////////////////////////////////////////////////////////////////////////
1237// PluginUrlRequestDelegate implementation.
1238// Forward network related responses to Chrome.
1239
1240void ChromeFrameAutomationClient::OnResponseStarted(
1241    int request_id, const char* mime_type,  const char* headers, int size,
1242    base::Time last_modified, const std::string& redirect_url,
1243    int redirect_status, const net::HostPortPair& socket_address,
1244    uint64 upload_size) {
1245  AutomationURLResponse response;
1246  response.mime_type = mime_type;
1247  if (headers)
1248    response.headers = headers;
1249  response.content_length = size;
1250  response.last_modified = last_modified;
1251  response.redirect_url = redirect_url;
1252  response.redirect_status = redirect_status;
1253  response.socket_address = socket_address;
1254  response.upload_size = upload_size;
1255
1256  automation_server_->Send(new AutomationMsg_RequestStarted(
1257      tab_->handle(), request_id, response));
1258}
1259
1260void ChromeFrameAutomationClient::OnReadComplete(int request_id,
1261                                                 const std::string& data) {
1262  automation_server_->Send(new AutomationMsg_RequestData(
1263      tab_->handle(), request_id, data));
1264}
1265
1266void ChromeFrameAutomationClient::OnResponseEnd(
1267    int request_id,
1268    const net::URLRequestStatus& status) {
1269  automation_server_->Send(new AutomationMsg_RequestEnd(
1270      tab_->handle(), request_id, status));
1271}
1272