Home | History | Annotate | Download | only in chrome_frame
      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 
    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).
   1003 bool 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 
   1020 void 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 
   1032 void 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 
   1040 void 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 
   1052 void 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 
   1067 void 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 
   1076 void 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 
   1109 void 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 
   1139 void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
   1140   int selected_command) {
   1141   if (tab_)
   1142     tab_->SendContextMenuCommand(selected_command);
   1143 }
   1144 
   1145 std::wstring ChromeFrameAutomationClient::GetVersion() const {
   1146   return GetCurrentModuleVersion();
   1147 }
   1148 
   1149 void 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 
   1167 void ChromeFrameAutomationClient::PrintTab() {
   1168   if (tab_)
   1169     tab_->PrintAsync();
   1170 }
   1171 
   1172 void 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 
   1180 void 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 
   1188 void 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 
   1201 void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask) {
   1202   automation_server_->Send(new AutomationMsg_RemoveBrowsingData(remove_mask));
   1203 }
   1204 
   1205 void 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 
   1213 void 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 
   1220 void 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 
   1240 void 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 
   1260 void 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 
   1266 void 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