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:
TabProxyNotificationMessageFilter(AutomationHandleTracker * tracker)49 explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker)
50 : tracker_(tracker) {
51 }
52
AddTabProxy(AutomationHandle tab_proxy)53 void AddTabProxy(AutomationHandle tab_proxy) {
54 base::AutoLock lock(lock_);
55 tabs_list_.push_back(tab_proxy);
56 }
57
RemoveTabProxy(AutomationHandle tab_proxy)58 void RemoveTabProxy(AutomationHandle tab_proxy) {
59 base::AutoLock lock(lock_);
60 tabs_list_.remove(tab_proxy);
61 }
62
OnMessageReceived(const IPC::Message & message)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
OnChannelError()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:
CFMsgDispatcher()106 CFMsgDispatcher() : SyncMessageReplyDispatcher() {}
107 protected:
HandleMessageType(const IPC::Message & msg,SyncMessageCallContext * context)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
ChromeFrameAutomationProxyImpl(AutomationProxyCacheEntry * entry,std::string channel_id,base::TimeDelta launch_timeout)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
~ChromeFrameAutomationProxyImpl()145 ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
146 TRACE_EVENT_END_ETW("chromeframe.automationproxy", this, "");
147 }
148
SendAsAsync(IPC::SyncMessage * msg,SyncMessageReplyDispatcher::SyncMessageCallContext * context,void * key)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
CancelAsync(void * key)156 void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) {
157 sync_->Cancel(key);
158 }
159
OnChannelError()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
CreateTabProxy(int handle)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
ReleaseTabProxy(AutomationHandle handle)178 void ChromeFrameAutomationProxyImpl::ReleaseTabProxy(AutomationHandle handle) {
179 message_filter_->RemoveTabProxy(handle);
180 }
181
182 struct LaunchTimeStats {
183 #ifndef NDEBUG
LaunchTimeStatsLaunchTimeStats184 LaunchTimeStats() {
185 launch_time_begin_ = base::Time::Now();
186 }
187
DumpLaunchTimeStats188 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
AutomationProxyCacheEntry(ChromeFrameLaunchParams * params,LaunchDelegate * delegate)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
~AutomationProxyCacheEntry()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
CreateProxy(ChromeFrameLaunchParams * params,LaunchDelegate * delegate)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
RemoveDelegate(LaunchDelegate * delegate,base::WaitableEvent * done,bool * was_last_delegate)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
AddDelegate(LaunchDelegate * delegate)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
OnChannelError()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
ProxyFactory()419 ProxyFactory::ProxyFactory() {
420 }
421
~ProxyFactory()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
GetAutomationServer(LaunchDelegate * delegate,ChromeFrameLaunchParams * params,void ** automation_server_id)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
ReleaseAutomationServer(void * server_id,LaunchDelegate * delegate)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
ChromeFrameAutomationClient()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
~ChromeFrameAutomationClient()543 ChromeFrameAutomationClient::~ChromeFrameAutomationClient() {
544 // Uninitialize must be called prior to the destructor
545 DCHECK(automation_server_ == NULL);
546 }
547
Initialize(ChromeFrameDelegate * chrome_frame_delegate,ChromeFrameLaunchParams * chrome_launch_params)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
Uninitialize()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
InitiateNavigation(const std::string & url,const std::string & referrer,NavigationConstraints * navigation_constraints)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
NavigateToIndex(int index)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
ForwardMessageFromExternalHost(const std::string & message,const std::string & origin,const std::string & target)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
SetProxySettings(const std::string & json_encoded_proxy_settings)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
BeginNavigate()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
BeginNavigateCompleted(AutomationMsg_NavigationResponseValues result)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
FindInPage(const std::wstring & search_string,FindInPageDirection forward,FindInPageCase match_case,bool find_next)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
OnChromeFrameHostMoved()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
CreateExternalTab()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
CreateExternalTabComplete(HWND chrome_window,HWND tab_window,int tab_handle,int session_id)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.
LaunchComplete(ChromeFrameAutomationProxy * proxy,AutomationLaunchResult result)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.
AutomationServerDied()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
InitializeComplete(AutomationLaunchResult result)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
ProcessUrlRequestMessage(TabProxy * tab,const IPC::Message & msg,bool ui_thread)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).
OnMessageReceived(TabProxy * tab,const IPC::Message & 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
OnChannelError(TabProxy * tab)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
OnMessageReceivedUIThread(const IPC::Message & msg)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
OnChannelErrorUIThread()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
ReportNavigationError(AutomationMsg_NavigationResponseValues error_code,const std::string & url)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
Resize(int width,int height,int flags)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
SetParentWindow(HWND parent_window)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
ReleaseAutomationServer()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
SendContextMenuCommandToChromeFrame(int selected_command)1139 void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
1140 int selected_command) {
1141 if (tab_)
1142 tab_->SendContextMenuCommand(selected_command);
1143 }
1144
GetVersion() const1145 std::wstring ChromeFrameAutomationClient::GetVersion() const {
1146 return GetCurrentModuleVersion();
1147 }
1148
Print(HDC print_dc,const RECT & print_bounds)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
PrintTab()1167 void ChromeFrameAutomationClient::PrintTab() {
1168 if (tab_)
1169 tab_->PrintAsync();
1170 }
1171
AttachExternalTab(uint64 external_tab_cookie)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
BlockExternalTab(uint64 cookie)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
SetPageFontSize(enum AutomationPageFontSize font_size)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
RemoveBrowsingData(int remove_mask)1201 void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask) {
1202 automation_server_->Send(new AutomationMsg_RemoveBrowsingData(remove_mask));
1203 }
1204
SetUrlFetcher(PluginUrlRequestManager * url_fetcher)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
SetZoomLevel(content::PageZoom zoom_level)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
OnUnload(bool * should_unload)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
OnResponseStarted(int request_id,const char * mime_type,const char * headers,int size,base::Time last_modified,const std::string & redirect_url,int redirect_status,const net::HostPortPair & socket_address,uint64 upload_size)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
OnReadComplete(int request_id,const std::string & data)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
OnResponseEnd(int request_id,const net::URLRequestStatus & status)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