- add sources.
[platform/framework/web/crosswalk.git] / src / chrome_frame / chrome_frame_automation.cc
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
36 namespace {
37
38 #ifdef NDEBUG
39 int64 kAutomationServerReasonableLaunchDelay = 1000;  // in milliseconds
40 #else
41 int64 kAutomationServerReasonableLaunchDelay = 1000 * 10;
42 #endif
43
44 }  // namespace
45
46 class 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
103 class 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
129 ChromeFrameAutomationProxyImpl::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
145 ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
146   TRACE_EVENT_END_ETW("chromeframe.automationproxy", this, "");
147 }
148
149 void 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
156 void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) {
157   sync_->Cancel(key);
158 }
159
160 void ChromeFrameAutomationProxyImpl::OnChannelError() {
161   DLOG(ERROR) << "Automation server died";
162   if (proxy_entry_) {
163     proxy_entry_->OnChannelError();
164   } else {
165     NOTREACHED();
166   }
167 }
168
169 scoped_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
178 void ChromeFrameAutomationProxyImpl::ReleaseTabProxy(AutomationHandle handle) {
179   message_filter_->RemoveTabProxy(handle);
180 }
181
182 struct 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
204 AutomationProxyCacheEntry::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
219 AutomationProxyCacheEntry::~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
233 void 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
364 void 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
398 void 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
410 void 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
419 ProxyFactory::ProxyFactory() {
420 }
421
422 ProxyFactory::~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
431 void 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
464 bool 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
519 static base::LazyInstance<ProxyFactory>::Leaky
520     g_proxy_factory = LAZY_INSTANCE_INITIALIZER;
521
522 ChromeFrameAutomationClient::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
543 ChromeFrameAutomationClient::~ChromeFrameAutomationClient() {
544   // Uninitialize must be called prior to the destructor
545   DCHECK(automation_server_ == NULL);
546 }
547
548 bool 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
610 void 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
648 bool 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
697 bool 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
712 bool 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
723 bool 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
731 void 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
760 void 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
767 void 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
789 void 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
800 void 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
834 AutomationLaunchResult 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.
858 void 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.
901 void 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
912 void 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
944 bool 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     case AutomationMsg_GetCookiesFromHost::ID:
990       if (ui_thread || (url_fetcher_flags_ &
991                           PluginUrlRequestManager::COOKIE_REQUEST_THREADSAFE)) {
992         AutomationMsg_GetCookiesFromHost::Dispatch(&msg, url_fetcher_, this,
993             &PluginUrlRequestManager::GetCookiesFromHost);
994         return true;
995       }
996       break;
997
998     case AutomationMsg_SetCookieAsync::ID:
999       if (ui_thread || (url_fetcher_flags_ &
1000                           PluginUrlRequestManager::COOKIE_REQUEST_THREADSAFE)) {
1001         AutomationMsg_SetCookieAsync::Dispatch(&msg, url_fetcher_, this,
1002             &PluginUrlRequestManager::SetCookiesInHost);
1003         return true;
1004       }
1005       break;
1006   }
1007
1008   PostTask(
1009       FROM_HERE,
1010       base::Bind(
1011           base::IgnoreResult(
1012               &ChromeFrameAutomationClient::ProcessUrlRequestMessage),
1013           base::Unretained(this), tab, msg, true));
1014   return true;
1015 }
1016
1017 // These are invoked in channel's background thread.
1018 // Cannot call any method of the activex here since it is a STA kind of being.
1019 // By default we marshal the IPC message to the main/GUI thread and from there
1020 // we safely invoke chrome_frame_delegate_->OnMessageReceived(msg).
1021 bool ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab,
1022                                                     const IPC::Message& msg) {
1023   DCHECK(tab == tab_.get());
1024   // Quickly process network related messages.
1025   if (url_fetcher_ && ProcessUrlRequestMessage(tab, msg, false))
1026     return true;
1027
1028   // Early check to avoid needless marshaling
1029   if (chrome_frame_delegate_ == NULL)
1030     return false;
1031
1032   PostTask(FROM_HERE,
1033            base::Bind(&ChromeFrameAutomationClient::OnMessageReceivedUIThread,
1034                       base::Unretained(this), msg));
1035   return true;
1036 }
1037
1038 void ChromeFrameAutomationClient::OnChannelError(TabProxy* tab) {
1039   DCHECK(tab == tab_.get());
1040   // Early check to avoid needless marshaling
1041   if (chrome_frame_delegate_ == NULL)
1042     return;
1043
1044   PostTask(
1045       FROM_HERE,
1046       base::Bind(&ChromeFrameAutomationClient::OnChannelErrorUIThread,
1047                  base::Unretained(this)));
1048 }
1049
1050 void ChromeFrameAutomationClient::OnMessageReceivedUIThread(
1051     const IPC::Message& msg) {
1052   DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_);
1053   // Forward to the delegate.
1054   if (chrome_frame_delegate_)
1055     chrome_frame_delegate_->OnMessageReceived(msg);
1056 }
1057
1058 void ChromeFrameAutomationClient::OnChannelErrorUIThread() {
1059   DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_);
1060
1061   // Report a metric that something went wrong unexpectedly.
1062   CrashMetricsReporter::GetInstance()->IncrementMetric(
1063       CrashMetricsReporter::CHANNEL_ERROR_COUNT);
1064
1065   // Forward to the delegate.
1066   if (chrome_frame_delegate_)
1067     chrome_frame_delegate_->OnChannelError();
1068 }
1069
1070 void ChromeFrameAutomationClient::ReportNavigationError(
1071     AutomationMsg_NavigationResponseValues error_code,
1072     const std::string& url) {
1073   if (!chrome_frame_delegate_)
1074     return;
1075
1076   if (ui_thread_id_ == base::PlatformThread::CurrentId()) {
1077     chrome_frame_delegate_->OnLoadFailed(error_code, url);
1078   } else {
1079     PostTask(FROM_HERE,
1080              base::Bind(&ChromeFrameAutomationClient::ReportNavigationError,
1081                         base::Unretained(this), error_code, url));
1082   }
1083 }
1084
1085 void ChromeFrameAutomationClient::Resize(int width, int height,
1086                                          int flags) {
1087   if (tab_.get() && ::IsWindow(chrome_window())) {
1088     SetWindowPos(HWND_TOP, 0, 0, width, height, flags);
1089     tab_->Reposition(chrome_window(), HWND_TOP, 0, 0, width, height,
1090                      flags, m_hWnd);
1091   }
1092 }
1093
1094 void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window) {
1095   parent_window_ = parent_window;
1096   // If we're done with the initialization step, go ahead
1097   if (is_initialized()) {
1098     if (parent_window == NULL) {
1099       // Hide and reparent the automation window. This window will get
1100       // reparented to the new ActiveX/Active document window when it gets
1101       // created.
1102       ShowWindow(SW_HIDE);
1103       SetParent(GetDesktopWindow());
1104     } else {
1105       if (!::IsWindow(chrome_window())) {
1106         DLOG(WARNING) << "Invalid Chrome Window handle in SetParentWindow";
1107         return;
1108       }
1109
1110       if (!SetParent(parent_window)) {
1111         DLOG(WARNING) << "Failed to set parent window for automation window. "
1112                       << "Error = "
1113                       << GetLastError();
1114         return;
1115       }
1116
1117       RECT parent_client_rect = {0};
1118       ::GetClientRect(parent_window, &parent_client_rect);
1119       int width = parent_client_rect.right - parent_client_rect.left;
1120       int height = parent_client_rect.bottom - parent_client_rect.top;
1121
1122       Resize(width, height, SWP_SHOWWINDOW | SWP_NOZORDER);
1123     }
1124   }
1125 }
1126
1127 void ChromeFrameAutomationClient::ReleaseAutomationServer() {
1128   if (automation_server_id_) {
1129     // Cache the server id and clear the automation_server_id_ before
1130     // calling ReleaseAutomationServer.  The reason we do this is that
1131     // we must cancel pending messages before we release the automation server.
1132     // Furthermore, while ReleaseAutomationServer is running, we could get
1133     // a callback to LaunchComplete which could cause an external tab to be
1134     // created. Ideally the callbacks should be dropped.
1135     // TODO(ananta)
1136     // Refactor the ChromeFrameAutomationProxy code to not depend on
1137     // AutomationProxy and simplify the whole mess.
1138     void* server_id = automation_server_id_;
1139     automation_server_id_ = NULL;
1140
1141     if (automation_server_) {
1142       // Make sure to clean up any pending sync messages before we go away.
1143       automation_server_->CancelAsync(this);
1144     }
1145
1146     proxy_factory_->ReleaseAutomationServer(server_id, this);
1147     automation_server_ = NULL;
1148
1149     // automation_server_ must not have been set to non NULL.
1150     // (if this regresses, start by looking at LaunchComplete()).
1151     DCHECK(automation_server_ == NULL);
1152   } else {
1153     DCHECK(automation_server_ == NULL);
1154   }
1155 }
1156
1157 void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
1158   int selected_command) {
1159   if (tab_)
1160     tab_->SendContextMenuCommand(selected_command);
1161 }
1162
1163 std::wstring ChromeFrameAutomationClient::GetVersion() const {
1164   return GetCurrentModuleVersion();
1165 }
1166
1167 void ChromeFrameAutomationClient::Print(HDC print_dc,
1168                                         const RECT& print_bounds) {
1169   if (!tab_window_) {
1170     NOTREACHED();
1171     return;
1172   }
1173
1174   HDC window_dc = ::GetDC(tab_window_);
1175
1176   BitBlt(print_dc, print_bounds.left, print_bounds.top,
1177          print_bounds.right - print_bounds.left,
1178          print_bounds.bottom - print_bounds.top,
1179          window_dc, print_bounds.left, print_bounds.top,
1180          SRCCOPY);
1181
1182   ::ReleaseDC(tab_window_, window_dc);
1183 }
1184
1185 void ChromeFrameAutomationClient::PrintTab() {
1186   if (tab_)
1187     tab_->PrintAsync();
1188 }
1189
1190 void ChromeFrameAutomationClient::AttachExternalTab(
1191     uint64 external_tab_cookie) {
1192   DCHECK_EQ(static_cast<TabProxy*>(NULL), tab_.get());
1193   DCHECK_EQ(-1, tab_handle_);
1194
1195   external_tab_cookie_ = external_tab_cookie;
1196 }
1197
1198 void ChromeFrameAutomationClient::BlockExternalTab(uint64 cookie) {
1199   // The host does not want this tab to be shown (due popup blocker).
1200   IPC::SyncMessage* message =
1201       new AutomationMsg_ConnectExternalTab(cookie, false, m_hWnd,
1202                                            NULL, NULL, NULL, 0);
1203   automation_server_->SendAsAsync(message, NULL, this);
1204 }
1205
1206 void ChromeFrameAutomationClient::SetPageFontSize(
1207     enum AutomationPageFontSize font_size) {
1208   if (font_size < SMALLEST_FONT ||
1209       font_size > LARGEST_FONT) {
1210       NOTREACHED() << "Invalid font size specified : "
1211                    << font_size;
1212       return;
1213   }
1214
1215   automation_server_->Send(
1216       new AutomationMsg_SetPageFontSize(tab_handle_, font_size));
1217 }
1218
1219 void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask) {
1220   automation_server_->Send(new AutomationMsg_RemoveBrowsingData(remove_mask));
1221 }
1222
1223 void ChromeFrameAutomationClient::SetUrlFetcher(
1224     PluginUrlRequestManager* url_fetcher) {
1225   DCHECK(url_fetcher != NULL);
1226   url_fetcher_ = url_fetcher;
1227   url_fetcher_flags_ = url_fetcher->GetThreadSafeFlags();
1228   url_fetcher_->set_delegate(this);
1229 }
1230
1231 void ChromeFrameAutomationClient::SetZoomLevel(content::PageZoom zoom_level) {
1232   if (automation_server_) {
1233     automation_server_->Send(new AutomationMsg_SetZoomLevel(tab_handle_,
1234                                                             zoom_level));
1235   }
1236 }
1237
1238 void ChromeFrameAutomationClient::OnUnload(bool* should_unload) {
1239   *should_unload = true;
1240   if (automation_server_) {
1241     const DWORD kUnloadEventTimeout = 20000;
1242
1243     IPC::SyncMessage* msg = new AutomationMsg_RunUnloadHandlers(tab_handle_,
1244                                                                 should_unload);
1245     base::WaitableEvent unload_call_finished(false, false);
1246     UnloadContext* unload_context = new UnloadContext(&unload_call_finished,
1247                                                       should_unload);
1248     automation_server_->SendAsAsync(msg, unload_context, this);
1249     HANDLE done = unload_call_finished.handle();
1250     WaitWithMessageLoop(&done, 1, kUnloadEventTimeout);
1251   }
1252 }
1253
1254 //////////////////////////////////////////////////////////////////////////
1255 // PluginUrlRequestDelegate implementation.
1256 // Forward network related responses to Chrome.
1257
1258 void ChromeFrameAutomationClient::OnResponseStarted(
1259     int request_id, const char* mime_type,  const char* headers, int size,
1260     base::Time last_modified, const std::string& redirect_url,
1261     int redirect_status, const net::HostPortPair& socket_address,
1262     uint64 upload_size) {
1263   AutomationURLResponse response;
1264   response.mime_type = mime_type;
1265   if (headers)
1266     response.headers = headers;
1267   response.content_length = size;
1268   response.last_modified = last_modified;
1269   response.redirect_url = redirect_url;
1270   response.redirect_status = redirect_status;
1271   response.socket_address = socket_address;
1272   response.upload_size = upload_size;
1273
1274   automation_server_->Send(new AutomationMsg_RequestStarted(
1275       tab_->handle(), request_id, response));
1276 }
1277
1278 void ChromeFrameAutomationClient::OnReadComplete(int request_id,
1279                                                  const std::string& data) {
1280   automation_server_->Send(new AutomationMsg_RequestData(
1281       tab_->handle(), request_id, data));
1282 }
1283
1284 void ChromeFrameAutomationClient::OnResponseEnd(
1285     int request_id,
1286     const net::URLRequestStatus& status) {
1287   automation_server_->Send(new AutomationMsg_RequestEnd(
1288       tab_->handle(), request_id, status));
1289 }
1290
1291 void ChromeFrameAutomationClient::OnCookiesRetrieved(bool success,
1292     const GURL& url, const std::string& cookie_string, int cookie_id) {
1293   automation_server_->Send(new AutomationMsg_GetCookiesHostResponse(
1294       tab_->handle(), success, url, cookie_string, cookie_id));
1295 }