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.
5 #include "chrome_frame/chrome_frame_automation.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_util.h"
14 #include "base/file_version_info.h"
15 #include "base/lazy_instance.h"
16 #include "base/logging.h"
17 #include "base/metrics/field_trial.h"
18 #include "base/path_service.h"
19 #include "base/process_util.h"
20 #include "base/string_util.h"
21 #include "base/synchronization/lock.h"
22 #include "base/synchronization/waitable_event.h"
23 #include "base/sys_info.h"
24 #include "base/utf_string_conversions.h"
25 #include "chrome/app/client_util.h"
26 #include "chrome/common/automation_messages.h"
27 #include "chrome/common/chrome_constants.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/test/automation/tab_proxy.h"
30 #include "chrome_frame/chrome_launcher_utils.h"
31 #include "chrome_frame/crash_reporting/crash_metrics.h"
32 #include "chrome_frame/custom_sync_call_context.h"
33 #include "chrome_frame/navigation_constraints.h"
34 #include "chrome_frame/simple_resource_loader.h"
35 #include "chrome_frame/utils.h"
36 #include "ui/base/ui_base_switches.h"
41 int64 kAutomationServerReasonableLaunchDelay
= 1000; // in milliseconds
43 int64 kAutomationServerReasonableLaunchDelay
= 1000 * 10;
46 const char kChromeShutdownDelaySeconds
[] = "30";
47 const char kWithDelayFieldTrialName
[] = "WithShutdownDelay";
48 const char kNoDelayFieldTrialName
[] = "NoShutdownDelay";
52 class ChromeFrameAutomationProxyImpl::TabProxyNotificationMessageFilter
53 : public IPC::ChannelProxy::MessageFilter
{
55 explicit TabProxyNotificationMessageFilter(AutomationHandleTracker
* tracker
)
59 void AddTabProxy(AutomationHandle tab_proxy
) {
60 base::AutoLock
lock(lock_
);
61 tabs_list_
.push_back(tab_proxy
);
64 void RemoveTabProxy(AutomationHandle tab_proxy
) {
65 base::AutoLock
lock(lock_
);
66 tabs_list_
.remove(tab_proxy
);
69 virtual bool OnMessageReceived(const IPC::Message
& message
) {
70 if (message
.is_reply())
73 if (!ChromeFrameDelegateImpl::IsTabMessage(message
))
76 // Get AddRef-ed pointer to corresponding TabProxy object
77 TabProxy
* tab
= static_cast<TabProxy
*>(tracker_
->GetResource(
78 message
.routing_id()));
81 handled
= tab
->OnMessageReceived(message
);
84 DLOG(ERROR
) << "Failed to find TabProxy for tab:" << message
.routing_id();
85 // To prevent subsequent crashes, we set handled to true in this case.
91 virtual void OnChannelError() {
92 std::list
<AutomationHandle
>::const_iterator iter
= tabs_list_
.begin();
93 for (; iter
!= tabs_list_
.end(); ++iter
) {
94 // Get AddRef-ed pointer to corresponding TabProxy object
95 TabProxy
* tab
= static_cast<TabProxy
*>(tracker_
->GetResource(*iter
));
97 tab
->OnChannelError();
104 AutomationHandleTracker
* tracker_
;
105 std::list
<AutomationHandle
> tabs_list_
;
109 class ChromeFrameAutomationProxyImpl::CFMsgDispatcher
110 : public SyncMessageReplyDispatcher
{
112 CFMsgDispatcher() : SyncMessageReplyDispatcher() {}
114 virtual bool HandleMessageType(const IPC::Message
& msg
,
115 SyncMessageCallContext
* context
) {
116 switch (context
->message_type()) {
117 case AutomationMsg_CreateExternalTab::ID
:
118 case AutomationMsg_ConnectExternalTab::ID
:
119 InvokeCallback
<CreateExternalTabContext
>(msg
, context
);
121 case AutomationMsg_NavigateExternalTabAtIndex::ID
:
122 case AutomationMsg_NavigateInExternalTab::ID
:
123 InvokeCallback
<BeginNavigateContext
>(msg
, context
);
125 case AutomationMsg_RunUnloadHandlers::ID
:
126 InvokeCallback
<UnloadContext
>(msg
, context
);
135 ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl(
136 AutomationProxyCacheEntry
* entry
,
137 std::string channel_id
, int launch_timeout
)
138 : AutomationProxy(launch_timeout
, false), proxy_entry_(entry
) {
139 TRACE_EVENT_BEGIN_ETW("chromeframe.automationproxy", this, "");
141 InitializeChannel(channel_id
, false);
143 sync_
= new CFMsgDispatcher();
144 message_filter_
= new TabProxyNotificationMessageFilter(tracker_
.get());
146 // Order of filters is not important.
147 channel_
->AddFilter(message_filter_
.get());
148 channel_
->AddFilter(sync_
.get());
151 ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() {
152 TRACE_EVENT_END_ETW("chromeframe.automationproxy", this, "");
155 void ChromeFrameAutomationProxyImpl::SendAsAsync(
156 IPC::SyncMessage
* msg
,
157 SyncMessageReplyDispatcher::SyncMessageCallContext
* context
, void* key
) {
158 sync_
->Push(msg
, context
, key
);
159 channel_
->ChannelProxy::Send(msg
);
162 void ChromeFrameAutomationProxyImpl::CancelAsync(void* key
) {
166 void ChromeFrameAutomationProxyImpl::OnChannelError() {
167 DLOG(ERROR
) << "Automation server died";
169 proxy_entry_
->OnChannelError();
175 scoped_refptr
<TabProxy
> ChromeFrameAutomationProxyImpl::CreateTabProxy(
177 DCHECK(tracker_
->GetResource(handle
) == NULL
);
178 TabProxy
* tab_proxy
= new TabProxy(this, tracker_
.get(), handle
);
179 if (tab_proxy
!= NULL
)
180 message_filter_
->AddTabProxy(handle
);
184 void ChromeFrameAutomationProxyImpl::ReleaseTabProxy(AutomationHandle handle
) {
185 message_filter_
->RemoveTabProxy(handle
);
188 struct LaunchTimeStats
{
191 launch_time_begin_
= base::Time::Now();
195 base::TimeDelta launch_time
= base::Time::Now() - launch_time_begin_
;
196 UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchTime", launch_time
);
197 const int64 launch_milliseconds
= launch_time
.InMilliseconds();
198 if (launch_milliseconds
> kAutomationServerReasonableLaunchDelay
) {
199 LOG(WARNING
) << "Automation server launch took longer than expected: " <<
200 launch_milliseconds
<< " ms.";
204 base::Time launch_time_begin_
;
210 AutomationProxyCacheEntry::AutomationProxyCacheEntry(
211 ChromeFrameLaunchParams
* params
, LaunchDelegate
* delegate
)
212 : profile_name(params
->profile_name()),
213 launch_result_(AUTOMATION_LAUNCH_RESULT_INVALID
) {
215 thread_
.reset(new base::Thread(WideToASCII(profile_name
).c_str()));
217 // Use scoped_refptr so that the params will get released when the task
219 scoped_refptr
<ChromeFrameLaunchParams
> ref_params(params
);
220 thread_
->message_loop()->PostTask(
221 FROM_HERE
, base::Bind(&AutomationProxyCacheEntry::CreateProxy
,
222 base::Unretained(this), ref_params
, delegate
));
225 AutomationProxyCacheEntry::~AutomationProxyCacheEntry() {
226 DVLOG(1) << __FUNCTION__
<< profile_name
;
227 // Attempt to fix chrome_frame_tests crash seen at times on the IE6/IE7
228 // builders. It appears that there are cases when we can enter here when the
229 // AtExitManager is tearing down the global ProxyCache which causes a crash
230 // while tearing down the AutomationProxy object due to a NULL MessageLoop
231 // The AutomationProxy class uses the SyncChannel which assumes the existence
232 // of a MessageLoop instance.
233 // We leak the AutomationProxy pointer here to avoid a crash.
234 if (MessageLoop::current() == NULL
) {
239 void AutomationProxyCacheEntry::CreateProxy(ChromeFrameLaunchParams
* params
,
240 LaunchDelegate
* delegate
) {
241 DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
244 DCHECK(proxy_
.get() == NULL
);
246 // We *must* create automationproxy in a thread that has message loop,
247 // since SyncChannel::Context construction registers event to be watched
248 // through ObjectWatcher which subscribes for the current thread message loop
249 // destruction notification.
251 // At same time we must destroy/stop the thread from another thread.
252 std::string channel_id
= AutomationProxy::GenerateChannelID();
253 ChromeFrameAutomationProxyImpl
* proxy
=
254 new ChromeFrameAutomationProxyImpl(this, channel_id
,
255 params
->launch_timeout());
257 // Ensure that the automation proxy actually respects our choice on whether
258 // or not to check the version.
259 proxy
->set_perform_version_check(params
->version_check());
262 std::wstring command_line_string
;
263 scoped_ptr
<CommandLine
> command_line
;
264 if (chrome_launcher::CreateLaunchCommandLine(&command_line
)) {
265 command_line
->AppendSwitchASCII(switches::kAutomationClientChannelID
,
268 // Run Chrome in Chrome Frame mode. In practice, this modifies the paths
269 // and registry keys that Chrome looks in via the BrowserDistribution
271 command_line
->AppendSwitch(switches::kChromeFrame
);
273 // Chrome Frame never wants Chrome to start up with a First Run UI.
274 command_line
->AppendSwitch(switches::kNoFirstRun
);
276 // Chrome Frame never wants to run background extensions since they
277 // interfere with in-use updates.
278 command_line
->AppendSwitch(switches::kDisableBackgroundMode
);
280 command_line
->AppendSwitch(switches::kDisablePopupBlocking
);
282 #if defined(GOOGLE_CHROME_BUILD)
283 // Chrome Frame should use the native print dialog.
284 command_line
->AppendSwitch(switches::kDisablePrintPreview
);
287 // Disable the "Whoa! Chrome has crashed." dialog, because that isn't very
288 // useful for Chrome Frame users.
290 command_line
->AppendSwitch(switches::kNoErrorDialogs
);
293 // In headless mode runs like reliability test runs we want full crash dumps
295 if (IsHeadlessMode())
296 command_line
->AppendSwitch(switches::kFullMemoryCrashReport
);
298 // In accessible mode automation tests expect renderer accessibility to be
299 // enabled in chrome.
300 if (IsAccessibleMode())
301 command_line
->AppendSwitch(switches::kForceRendererAccessibility
);
303 if (params
->send_shutdown_delay_switch())
304 command_line
->AppendSwitchASCII(switches::kChromeFrameShutdownDelay
,
305 kChromeShutdownDelaySeconds
);
307 DVLOG(1) << "Profile path: " << params
->profile_path().value();
308 command_line
->AppendSwitchPath(switches::kUserDataDir
,
309 params
->profile_path());
311 // Ensure that Chrome is running the specified version of chrome.dll.
312 command_line
->AppendSwitchNative(switches::kChromeVersion
,
313 GetCurrentModuleVersion());
315 if (!params
->language().empty())
316 command_line
->AppendSwitchNative(switches::kLang
, params
->language());
318 command_line_string
= command_line
->GetCommandLineString();
321 automation_server_launch_start_time_
= base::TimeTicks::Now();
323 if (command_line_string
.empty() ||
324 !base::LaunchProcess(command_line_string
, base::LaunchOptions(), NULL
)) {
325 // We have no code for launch failure.
326 launch_result_
= AUTOMATION_LAUNCH_RESULT_INVALID
;
328 // Launch timeout may happen if the new instance tries to communicate
329 // with an existing Chrome instance that is hung and displays msgbox
330 // asking to kill the previous one. This could be easily observed if the
331 // already running Chrome instance is running as high-integrity process
332 // (started with "Run as Administrator" or launched by another high
333 // integrity process) hence our medium-integrity process
334 // cannot SendMessage to it with request to activate itself.
336 // TODO(stoyan) AutomationProxy eats Hello message, hence installing
337 // message filter is pointless, we can leverage ObjectWatcher and use
338 // system thread pool to notify us when proxy->AppLaunch event is signaled.
339 LaunchTimeStats launch_stats
;
340 // Wait for the automation server launch result, then stash away the
341 // version string it reported.
342 launch_result_
= proxy
->WaitForAppLaunch();
345 base::TimeDelta delta
=
346 base::TimeTicks::Now() - automation_server_launch_start_time_
;
348 if (launch_result_
== AUTOMATION_SUCCESS
) {
350 "ChromeFrame.AutomationServerLaunchSuccessTime", delta
);
352 base::FieldTrial::MakeName(
353 "ChromeFrame.AutomationServerLaunchSuccessTime",
354 "ChromeShutdownDelay"),
358 "ChromeFrame.AutomationServerLaunchFailedTime", delta
);
360 base::FieldTrial::MakeName(
361 "ChromeFrame.AutomationServerLaunchFailedTime",
362 "ChromeShutdownDelay"),
366 UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.LaunchResult",
369 AUTOMATION_CREATE_TAB_FAILED
,
370 AUTOMATION_CREATE_TAB_FAILED
+ 1);
373 TRACE_EVENT_END_ETW("chromeframe.createproxy", this, "");
375 // Finally set the proxy.
377 launch_delegates_
.push_back(delegate
);
379 delegate
->LaunchComplete(proxy_
.get(), launch_result_
);
382 void AutomationProxyCacheEntry::RemoveDelegate(LaunchDelegate
* delegate
,
383 base::WaitableEvent
* done
,
384 bool* was_last_delegate
) {
385 DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
388 DCHECK(was_last_delegate
);
390 *was_last_delegate
= false;
392 LaunchDelegates::iterator it
= std::find(launch_delegates_
.begin(),
393 launch_delegates_
.end(), delegate
);
394 if (it
== launch_delegates_
.end()) {
397 if (launch_delegates_
.size() == 1) {
398 *was_last_delegate
= true;
400 // Process pending notifications.
401 thread_
->message_loop()->RunAllPending();
403 // Take down the proxy since we no longer have any clients.
404 // Make sure we only do this once all pending messages have been cleared.
407 // Be careful to remove from the list after running pending
408 // tasks. Otherwise the delegate being removed might miss out
409 // on pending notifications such as LaunchComplete.
410 launch_delegates_
.erase(it
);
416 void AutomationProxyCacheEntry::AddDelegate(LaunchDelegate
* delegate
) {
417 DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
418 DCHECK(std::find(launch_delegates_
.begin(),
419 launch_delegates_
.end(),
420 delegate
) == launch_delegates_
.end())
421 << "Same delegate being added twice";
422 DCHECK(launch_result_
!= AUTOMATION_LAUNCH_RESULT_INVALID
);
424 launch_delegates_
.push_back(delegate
);
425 delegate
->LaunchComplete(proxy_
.get(), launch_result_
);
428 void AutomationProxyCacheEntry::OnChannelError() {
429 DCHECK(IsSameThread(base::PlatformThread::CurrentId()));
430 launch_result_
= AUTOMATION_SERVER_CRASHED
;
431 LaunchDelegates::const_iterator it
= launch_delegates_
.begin();
432 for (; it
!= launch_delegates_
.end(); ++it
) {
433 (*it
)->AutomationServerDied();
437 ProxyFactory::ProxyFactory() {
440 ProxyFactory::~ProxyFactory() {
441 for (size_t i
= 0; i
< proxies_
.container().size(); ++i
) {
442 DWORD result
= proxies_
[i
]->WaitForThread(0);
443 if (WAIT_OBJECT_0
!= result
)
444 // TODO(stoyan): Don't leak proxies on exit.
445 DLOG(ERROR
) << "Proxies leaked on exit.";
449 void ProxyFactory::GetAutomationServer(
450 LaunchDelegate
* delegate
, ChromeFrameLaunchParams
* params
,
451 void** automation_server_id
) {
452 TRACE_EVENT_BEGIN_ETW("chromeframe.createproxy", this, "");
454 scoped_refptr
<AutomationProxyCacheEntry
> entry
;
455 // Find already existing launcher thread for given profile
456 base::AutoLock
lock(lock_
);
457 for (size_t i
= 0; i
< proxies_
.container().size(); ++i
) {
458 if (proxies_
[i
]->IsSameProfile(params
->profile_name())) {
465 DVLOG(1) << __FUNCTION__
<< " creating new proxy entry";
466 entry
= new AutomationProxyCacheEntry(params
, delegate
);
467 proxies_
.container().push_back(entry
);
468 } else if (delegate
) {
469 // Notify the new delegate of the launch status from the worker thread
470 // and add it to the list of delegates.
471 entry
->message_loop()->PostTask(
472 FROM_HERE
, base::Bind(&AutomationProxyCacheEntry::AddDelegate
,
473 base::Unretained(entry
.get()), delegate
));
476 DCHECK(automation_server_id
!= NULL
);
477 DCHECK(!entry
->IsSameThread(base::PlatformThread::CurrentId()));
479 *automation_server_id
= entry
;
482 bool ProxyFactory::ReleaseAutomationServer(void* server_id
,
483 LaunchDelegate
* delegate
) {
489 AutomationProxyCacheEntry
* entry
=
490 reinterpret_cast<AutomationProxyCacheEntry
*>(server_id
);
494 Vector::ContainerType::iterator it
= std::find(proxies_
.container().begin(),
495 proxies_
.container().end(),
497 DCHECK(it
!= proxies_
.container().end());
498 DCHECK(!entry
->IsSameThread(base::PlatformThread::CurrentId()));
503 // AddRef the entry object as we might need to take it out of the proxy
504 // stack and then uninitialize the entry.
507 bool last_delegate
= false;
509 base::WaitableEvent
done(true, false);
510 entry
->message_loop()->PostTask(
512 base::Bind(&AutomationProxyCacheEntry::RemoveDelegate
,
513 base::Unretained(entry
), delegate
, &done
, &last_delegate
));
519 Vector::ContainerType::iterator it
= std::find(proxies_
.container().begin(),
520 proxies_
.container().end(),
522 if (it
!= proxies_
.container().end()) {
523 proxies_
.container().erase(it
);
525 DLOG(ERROR
) << "Proxy wasn't found. Proxy map is likely empty (size="
526 << proxies_
.container().size() << ").";
537 static base::LazyInstance
<ProxyFactory
>::Leaky
538 g_proxy_factory
= LAZY_INSTANCE_INITIALIZER
;
540 ChromeFrameAutomationClient::ChromeFrameAutomationClient()
541 : chrome_frame_delegate_(NULL
),
542 chrome_window_(NULL
),
544 parent_window_(NULL
),
545 automation_server_(NULL
),
546 automation_server_id_(NULL
),
548 init_state_(UNINITIALIZED
),
549 use_chrome_network_(false),
550 proxy_factory_(g_proxy_factory
.Pointer()),
551 handle_top_level_requests_(false),
554 external_tab_cookie_(0),
556 url_fetcher_flags_(PluginUrlRequestManager::NOT_THREADSAFE
),
557 navigate_after_initialization_(false),
558 route_all_top_level_navigations_(false),
559 send_shutdown_delay_switch_(true) {
560 InitializeFieldTrials();
563 ChromeFrameAutomationClient::~ChromeFrameAutomationClient() {
564 // Uninitialize must be called prior to the destructor
565 DCHECK(automation_server_
== NULL
);
568 bool ChromeFrameAutomationClient::Initialize(
569 ChromeFrameDelegate
* chrome_frame_delegate
,
570 ChromeFrameLaunchParams
* chrome_launch_params
) {
572 chrome_frame_delegate_
= chrome_frame_delegate
;
575 if (chrome_launch_params_
&& chrome_launch_params_
!= chrome_launch_params
) {
576 DCHECK_EQ(chrome_launch_params_
->url(), chrome_launch_params
->url());
577 DCHECK_EQ(chrome_launch_params_
->referrer(),
578 chrome_launch_params
->referrer());
582 chrome_launch_params_
= chrome_launch_params
;
584 ui_thread_id_
= base::PlatformThread::CurrentId();
586 // In debug mode give more time to work with a debugger.
587 if (IsDebuggerPresent()) {
588 // Don't use INFINITE (which is -1) or even MAXINT since we will convert
589 // from milliseconds to microseconds when stored in a base::TimeDelta,
590 // thus * 1000. An hour should be enough.
591 chrome_launch_params_
->set_launch_timeout(60 * 60 * 1000);
593 DCHECK_LT(chrome_launch_params_
->launch_timeout(),
595 chrome_launch_params_
->set_launch_timeout(
596 chrome_launch_params_
->launch_timeout() * 2);
600 // Create a window on the UI thread for marshaling messages back and forth
601 // from the IPC thread. This window cannot be a message only window as the
602 // external chrome tab window is created as a child of this window. This
603 // window is eventually reparented to the ActiveX plugin window.
604 if (!Create(GetDesktopWindow(), NULL
, NULL
,
605 WS_CHILDWINDOW
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS
,
611 // Keep object in memory, while the window is alive.
612 // Corresponding Release is in OnFinalMessage();
615 // Mark our state as initializing. We'll reach initialized once
616 // InitializeComplete is called successfully.
617 init_state_
= INITIALIZING
;
621 if (chrome_launch_params_
->url().is_valid())
622 navigate_after_initialization_
= false;
624 proxy_factory_
->GetAutomationServer(static_cast<LaunchDelegate
*>(this),
625 chrome_launch_params_
, &automation_server_id_
);
630 void ChromeFrameAutomationClient::Uninitialize() {
631 if (init_state_
== UNINITIALIZED
) {
632 DLOG(WARNING
) << __FUNCTION__
<< ": Automation client not initialized";
636 init_state_
= UNINITIALIZING
;
638 // Called from client's FinalRelease() / destructor
640 // Clean up any outstanding requests
641 url_fetcher_
->StopAllRequests();
646 tab_
->RemoveObserver(this);
647 if (automation_server_
)
648 automation_server_
->ReleaseTabProxy(tab_
->handle());
649 tab_
= NULL
; // scoped_refptr::Release
652 // Wait for the automation proxy's worker thread to exit.
653 ReleaseAutomationServer();
655 // We must destroy the window, since if there are pending tasks
656 // window procedure may be invoked after DLL is unloaded.
657 // Unfortunately pending tasks are leaked.
658 if (::IsWindow(m_hWnd
))
661 // DCHECK(navigate_after_initialization_ == false);
662 handle_top_level_requests_
= false;
664 chrome_frame_delegate_
= NULL
;
665 init_state_
= UNINITIALIZED
;
668 bool ChromeFrameAutomationClient::InitiateNavigation(
669 const std::string
& url
,
670 const std::string
& referrer
,
671 NavigationConstraints
* navigation_constraints
) {
675 GURL
parsed_url(url
);
677 // Catch invalid URLs early.
678 // Can we allow this navigation to happen?
679 if (!CanNavigate(parsed_url
, navigation_constraints
)) {
680 DLOG(ERROR
) << __FUNCTION__
<< " Not allowing navigation to: " << url
;
684 // If we are not yet initialized ignore attempts to navigate to the same url.
685 // Navigation attempts to the same URL could occur if the automation client
686 // was reused for a new active document instance.
687 if (!chrome_launch_params_
|| is_initialized() ||
688 parsed_url
!= chrome_launch_params_
->url()) {
689 // Important: Since we will be using the referrer_ variable from a
690 // different thread, we need to force a new std::string buffer instance for
691 // the referrer_ GURL variable. Otherwise we can run into strangeness when
692 // the GURL is accessed and it could result in a bad URL that can cause the
693 // referrer to be dropped or something worse.
694 GURL
referrer_gurl(referrer
.c_str());
695 if (!chrome_launch_params_
) {
696 FilePath profile_path
;
697 chrome_launch_params_
= new ChromeFrameLaunchParams(parsed_url
,
698 referrer_gurl
, profile_path
, L
"", SimpleResourceLoader::GetLanguage(),
699 false, false, route_all_top_level_navigations_
,
700 send_shutdown_delay_switch_
);
702 chrome_launch_params_
->set_referrer(referrer_gurl
);
703 chrome_launch_params_
->set_url(parsed_url
);
706 navigate_after_initialization_
= false;
708 if (is_initialized()) {
711 navigate_after_initialization_
= true;
718 bool ChromeFrameAutomationClient::NavigateToIndex(int index
) {
719 // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
720 if (!automation_server_
|| !tab_
.get() || !tab_
->is_valid()) {
724 DCHECK(::IsWindow(chrome_window_
));
726 IPC::SyncMessage
* msg
= new AutomationMsg_NavigateExternalTabAtIndex(
727 tab_
->handle(), index
, NULL
);
728 automation_server_
->SendAsAsync(msg
, new BeginNavigateContext(this),
733 bool ChromeFrameAutomationClient::ForwardMessageFromExternalHost(
734 const std::string
& message
, const std::string
& origin
,
735 const std::string
& target
) {
736 // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
737 if (!is_initialized())
740 tab_
->HandleMessageFromExternalHost(message
, origin
, target
);
744 bool ChromeFrameAutomationClient::SetProxySettings(
745 const std::string
& json_encoded_proxy_settings
) {
746 if (!is_initialized())
748 automation_server_
->SendProxyConfig(json_encoded_proxy_settings
);
752 void ChromeFrameAutomationClient::BeginNavigate() {
753 // Could be NULL if we failed to launch Chrome in LaunchAutomationServer()
754 if (!automation_server_
|| !tab_
.get()) {
755 DLOG(WARNING
) << "BeginNavigate - can't navigate.";
756 ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR
,
757 chrome_launch_params_
->url().spec());
761 DCHECK(::IsWindow(chrome_window_
));
763 if (!tab_
->is_valid()) {
764 DLOG(WARNING
) << "BeginNavigate - tab isn't valid.";
768 IPC::SyncMessage
* msg
=
769 new AutomationMsg_NavigateInExternalTab(tab_
->handle(),
770 chrome_launch_params_
->url(), chrome_launch_params_
->referrer(),
772 automation_server_
->SendAsAsync(msg
, new BeginNavigateContext(this), this);
774 RECT client_rect
= {0};
775 chrome_frame_delegate_
->GetBounds(&client_rect
);
776 Resize(client_rect
.right
- client_rect
.left
,
777 client_rect
.bottom
- client_rect
.top
,
778 SWP_NOACTIVATE
| SWP_NOZORDER
);
781 void ChromeFrameAutomationClient::BeginNavigateCompleted(
782 AutomationMsg_NavigationResponseValues result
) {
783 if (result
== AUTOMATION_MSG_NAVIGATION_ERROR
)
784 ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR
,
785 chrome_launch_params_
->url().spec());
788 void ChromeFrameAutomationClient::FindInPage(const std::wstring
& search_string
,
789 FindInPageDirection forward
,
790 FindInPageCase match_case
,
792 // Note that we can be called by the find dialog after the tab has gone away.
796 // What follows is quite similar to TabProxy::FindInPage() but uses
797 // the SyncMessageReplyDispatcher to avoid concerns about blocking
798 // synchronous messages.
799 AutomationMsg_Find_Params params
;
800 params
.search_string
= WideToUTF16Hack(search_string
);
801 params
.find_next
= find_next
;
802 params
.match_case
= (match_case
== CASE_SENSITIVE
);
803 params
.forward
= (forward
== FWD
);
805 IPC::SyncMessage
* msg
=
806 new AutomationMsg_Find(tab_
->handle(), params
, NULL
, NULL
);
807 automation_server_
->SendAsAsync(msg
, NULL
, this);
810 void ChromeFrameAutomationClient::OnChromeFrameHostMoved() {
811 // Use a local var to avoid the small possibility of getting the tab_
812 // member be cleared while we try to use it.
813 // Note that TabProxy is a RefCountedThreadSafe object, so we should be OK.
814 scoped_refptr
<TabProxy
> tab(tab_
);
815 // There also is a possibility that tab_ has not been set yet,
816 // so we still need to test for NULL.
821 void ChromeFrameAutomationClient::CreateExternalTab() {
822 AutomationLaunchResult launch_result
= AUTOMATION_SUCCESS
;
824 DCHECK(automation_server_
!= NULL
);
826 if (chrome_launch_params_
->url().is_valid()) {
827 navigate_after_initialization_
= false;
830 ExternalTabSettings settings
;
831 settings
.parent
= m_hWnd
;
832 settings
.style
= WS_CHILD
;
833 settings
.is_incognito
= chrome_launch_params_
->incognito();
834 settings
.load_requests_via_automation
= !use_chrome_network_
;
835 settings
.handle_top_level_requests
= handle_top_level_requests_
;
836 settings
.initial_url
= chrome_launch_params_
->url();
837 settings
.referrer
= chrome_launch_params_
->referrer();
838 // Infobars disabled in widget mode.
839 settings
.infobars_enabled
= !chrome_launch_params_
->widget_mode();
840 settings
.route_all_top_level_navigations
=
841 chrome_launch_params_
->route_all_top_level_navigations();
843 UMA_HISTOGRAM_CUSTOM_COUNTS(
844 "ChromeFrame.HostNetworking", !use_chrome_network_
, 1, 2, 3);
846 UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.HandleTopLevelRequests",
847 handle_top_level_requests_
, 1, 2, 3);
849 IPC::SyncMessage
* message
=
850 new AutomationMsg_CreateExternalTab(settings
, NULL
, NULL
, 0, 0);
851 automation_server_
->SendAsAsync(message
, new CreateExternalTabContext(this),
855 AutomationLaunchResult
ChromeFrameAutomationClient::CreateExternalTabComplete(
856 HWND chrome_window
, HWND tab_window
, int tab_handle
, int session_id
) {
857 if (!automation_server_
) {
858 // If we receive this notification while shutting down, do nothing.
859 DLOG(ERROR
) << "CreateExternalTabComplete called when automation server "
861 return AUTOMATION_CREATE_TAB_FAILED
;
864 AutomationLaunchResult launch_result
= AUTOMATION_SUCCESS
;
865 if (tab_handle
== 0 || !::IsWindow(chrome_window
)) {
866 launch_result
= AUTOMATION_CREATE_TAB_FAILED
;
868 chrome_window_
= chrome_window
;
869 tab_window_
= tab_window
;
870 tab_
= automation_server_
->CreateTabProxy(tab_handle
);
871 tab_
->AddObserver(this);
872 tab_handle_
= tab_handle
;
873 session_id_
= session_id
;
875 return launch_result
;
878 // Invoked in the automation proxy's worker thread.
879 void ChromeFrameAutomationClient::LaunchComplete(
880 ChromeFrameAutomationProxy
* proxy
,
881 AutomationLaunchResult result
) {
882 // If we're shutting down we don't keep a pointer to the automation server.
883 if (init_state_
!= UNINITIALIZING
) {
884 DCHECK(init_state_
== INITIALIZING
);
885 automation_server_
= proxy
;
887 DVLOG(1) << "Not storing automation server pointer due to shutting down";
890 if (result
== AUTOMATION_SUCCESS
) {
891 // NOTE: A potential problem here is that Uninitialize() may just have
892 // been called so we need to be careful and check the automation_server_
894 if (automation_server_
!= NULL
) {
895 // If we have a valid tab_handle here it means that we are attaching to
896 // an existing ExternalTabContainer instance, in which case we don't
897 // want to create an external tab instance in Chrome.
898 if (external_tab_cookie_
== 0) {
899 // Continue with Initialization - Create external tab
902 // Send a notification to Chrome that we are ready to connect to the
904 IPC::SyncMessage
* message
=
905 new AutomationMsg_ConnectExternalTab(external_tab_cookie_
, true,
906 m_hWnd
, NULL
, NULL
, NULL
, 0);
907 automation_server_
->SendAsAsync(message
,
908 new CreateExternalTabContext(this),
910 DVLOG(1) << __FUNCTION__
<< ": sending CreateExternalTabComplete";
914 // Launch failed. Note, we cannot delete proxy here.
916 base::Bind(&ChromeFrameAutomationClient::InitializeComplete
,
917 base::Unretained(this), result
));
921 // Invoked in the automation proxy's worker thread.
922 void ChromeFrameAutomationClient::AutomationServerDied() {
923 // Make sure we notify our delegate.
925 FROM_HERE
, base::Bind(&ChromeFrameAutomationClient::InitializeComplete
,
926 base::Unretained(this), AUTOMATION_SERVER_CRASHED
));
927 // Then uninitialize.
929 FROM_HERE
, base::Bind(&ChromeFrameAutomationClient::Uninitialize
,
930 base::Unretained(this)));
933 void ChromeFrameAutomationClient::InitializeComplete(
934 AutomationLaunchResult result
) {
935 DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_
);
936 if (result
!= AUTOMATION_SUCCESS
) {
937 DLOG(WARNING
) << "InitializeComplete: failure " << result
;
939 init_state_
= INITIALIZED
;
941 // If the host already have a window, ask Chrome to re-parent.
943 SetParentWindow(parent_window_
);
945 // If host specified destination URL - navigate. Apparently we do not use
946 // accelerator table.
947 if (navigate_after_initialization_
) {
948 navigate_after_initialization_
= false;
953 if (chrome_frame_delegate_
) {
954 if (result
== AUTOMATION_SUCCESS
) {
955 chrome_frame_delegate_
->OnAutomationServerReady();
958 if (automation_server_
)
959 version
= automation_server_
->server_version();
960 chrome_frame_delegate_
->OnAutomationServerLaunchFailed(result
, version
);
965 bool ChromeFrameAutomationClient::ProcessUrlRequestMessage(TabProxy
* tab
,
966 const IPC::Message
& msg
, bool ui_thread
) {
967 // Either directly call appropriate url_fetcher function
968 // or postpone call to the UI thread.
969 uint16 msg_type
= msg
.type();
974 case AutomationMsg_RequestStart::ID
:
975 if (ui_thread
|| (url_fetcher_flags_
&
976 PluginUrlRequestManager::START_REQUEST_THREADSAFE
)) {
977 AutomationMsg_RequestStart::Dispatch(&msg
, url_fetcher_
, this,
978 &PluginUrlRequestManager::StartUrlRequest
);
983 case AutomationMsg_RequestRead::ID
:
984 if (ui_thread
|| (url_fetcher_flags_
&
985 PluginUrlRequestManager::READ_REQUEST_THREADSAFE
)) {
986 AutomationMsg_RequestRead::Dispatch(&msg
, url_fetcher_
, this,
987 &PluginUrlRequestManager::ReadUrlRequest
);
992 case AutomationMsg_RequestEnd::ID
:
993 if (ui_thread
|| (url_fetcher_flags_
&
994 PluginUrlRequestManager::STOP_REQUEST_THREADSAFE
)) {
995 AutomationMsg_RequestEnd::Dispatch(&msg
, url_fetcher_
, this,
996 &PluginUrlRequestManager::EndUrlRequest
);
1001 case AutomationMsg_DownloadRequestInHost::ID
:
1002 if (ui_thread
|| (url_fetcher_flags_
&
1003 PluginUrlRequestManager::DOWNLOAD_REQUEST_THREADSAFE
)) {
1004 AutomationMsg_DownloadRequestInHost::Dispatch(&msg
, url_fetcher_
, this,
1005 &PluginUrlRequestManager::DownloadUrlRequestInHost
);
1010 case AutomationMsg_GetCookiesFromHost::ID
:
1011 if (ui_thread
|| (url_fetcher_flags_
&
1012 PluginUrlRequestManager::COOKIE_REQUEST_THREADSAFE
)) {
1013 AutomationMsg_GetCookiesFromHost::Dispatch(&msg
, url_fetcher_
, this,
1014 &PluginUrlRequestManager::GetCookiesFromHost
);
1019 case AutomationMsg_SetCookieAsync::ID
:
1020 if (ui_thread
|| (url_fetcher_flags_
&
1021 PluginUrlRequestManager::COOKIE_REQUEST_THREADSAFE
)) {
1022 AutomationMsg_SetCookieAsync::Dispatch(&msg
, url_fetcher_
, this,
1023 &PluginUrlRequestManager::SetCookiesInHost
);
1033 &ChromeFrameAutomationClient::ProcessUrlRequestMessage
),
1034 base::Unretained(this), tab
, msg
, true));
1038 void ChromeFrameAutomationClient::InitializeFieldTrials() {
1039 static base::FieldTrial
* trial
= NULL
;
1041 // Do one-time initialization of the field trial here.
1042 // TODO(robertshield): End the field trial before March 7th 2013.
1043 scoped_refptr
<base::FieldTrial
> new_trial
=
1044 base::FieldTrialList::FactoryGetFieldTrial(
1045 "ChromeShutdownDelay", 1000, kWithDelayFieldTrialName
,
1048 // Be consistent for this client. Note that this will only have an effect
1049 // once the client id is persisted. See http://crbug.com/117188
1050 new_trial
->UseOneTimeRandomization();
1052 new_trial
->AppendGroup(kNoDelayFieldTrialName
, 500); // 50% without.
1054 trial
= new_trial
.get();
1057 // Take action depending of which group we randomly land in.
1058 if (trial
->group_name() == kWithDelayFieldTrialName
)
1059 send_shutdown_delay_switch_
= true;
1061 send_shutdown_delay_switch_
= false;
1065 // These are invoked in channel's background thread.
1066 // Cannot call any method of the activex here since it is a STA kind of being.
1067 // By default we marshal the IPC message to the main/GUI thread and from there
1068 // we safely invoke chrome_frame_delegate_->OnMessageReceived(msg).
1069 bool ChromeFrameAutomationClient::OnMessageReceived(TabProxy
* tab
,
1070 const IPC::Message
& msg
) {
1071 DCHECK(tab
== tab_
.get());
1072 // Quickly process network related messages.
1073 if (url_fetcher_
&& ProcessUrlRequestMessage(tab
, msg
, false))
1076 // Early check to avoid needless marshaling
1077 if (chrome_frame_delegate_
== NULL
)
1081 base::Bind(&ChromeFrameAutomationClient::OnMessageReceivedUIThread
,
1082 base::Unretained(this), msg
));
1086 void ChromeFrameAutomationClient::OnChannelError(TabProxy
* tab
) {
1087 DCHECK(tab
== tab_
.get());
1088 // Early check to avoid needless marshaling
1089 if (chrome_frame_delegate_
== NULL
)
1094 base::Bind(&ChromeFrameAutomationClient::OnChannelErrorUIThread
,
1095 base::Unretained(this)));
1098 void ChromeFrameAutomationClient::OnMessageReceivedUIThread(
1099 const IPC::Message
& msg
) {
1100 DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_
);
1101 // Forward to the delegate.
1102 if (chrome_frame_delegate_
)
1103 chrome_frame_delegate_
->OnMessageReceived(msg
);
1106 void ChromeFrameAutomationClient::OnChannelErrorUIThread() {
1107 DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_
);
1109 // Report a metric that something went wrong unexpectedly.
1110 CrashMetricsReporter::GetInstance()->IncrementMetric(
1111 CrashMetricsReporter::CHANNEL_ERROR_COUNT
);
1113 // Forward to the delegate.
1114 if (chrome_frame_delegate_
)
1115 chrome_frame_delegate_
->OnChannelError();
1118 void ChromeFrameAutomationClient::ReportNavigationError(
1119 AutomationMsg_NavigationResponseValues error_code
,
1120 const std::string
& url
) {
1121 if (!chrome_frame_delegate_
)
1124 if (ui_thread_id_
== base::PlatformThread::CurrentId()) {
1125 chrome_frame_delegate_
->OnLoadFailed(error_code
, url
);
1128 base::Bind(&ChromeFrameAutomationClient::ReportNavigationError
,
1129 base::Unretained(this), error_code
, url
));
1133 void ChromeFrameAutomationClient::Resize(int width
, int height
,
1135 if (tab_
.get() && ::IsWindow(chrome_window())) {
1136 SetWindowPos(HWND_TOP
, 0, 0, width
, height
, flags
);
1137 tab_
->Reposition(chrome_window(), HWND_TOP
, 0, 0, width
, height
,
1142 void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window
) {
1143 parent_window_
= parent_window
;
1144 // If we're done with the initialization step, go ahead
1145 if (is_initialized()) {
1146 if (parent_window
== NULL
) {
1147 // Hide and reparent the automation window. This window will get
1148 // reparented to the new ActiveX/Active document window when it gets
1150 ShowWindow(SW_HIDE
);
1151 SetParent(GetDesktopWindow());
1153 if (!::IsWindow(chrome_window())) {
1154 DLOG(WARNING
) << "Invalid Chrome Window handle in SetParentWindow";
1158 if (!SetParent(parent_window
)) {
1159 DLOG(WARNING
) << "Failed to set parent window for automation window. "
1165 RECT parent_client_rect
= {0};
1166 ::GetClientRect(parent_window
, &parent_client_rect
);
1167 int width
= parent_client_rect
.right
- parent_client_rect
.left
;
1168 int height
= parent_client_rect
.bottom
- parent_client_rect
.top
;
1170 Resize(width
, height
, SWP_SHOWWINDOW
| SWP_NOZORDER
);
1175 void ChromeFrameAutomationClient::ReleaseAutomationServer() {
1176 if (automation_server_id_
) {
1177 // Cache the server id and clear the automation_server_id_ before
1178 // calling ReleaseAutomationServer. The reason we do this is that
1179 // we must cancel pending messages before we release the automation server.
1180 // Furthermore, while ReleaseAutomationServer is running, we could get
1181 // a callback to LaunchComplete which could cause an external tab to be
1182 // created. Ideally the callbacks should be dropped.
1184 // Refactor the ChromeFrameAutomationProxy code to not depend on
1185 // AutomationProxy and simplify the whole mess.
1186 void* server_id
= automation_server_id_
;
1187 automation_server_id_
= NULL
;
1189 if (automation_server_
) {
1190 // Make sure to clean up any pending sync messages before we go away.
1191 automation_server_
->CancelAsync(this);
1194 proxy_factory_
->ReleaseAutomationServer(server_id
, this);
1195 automation_server_
= NULL
;
1197 // automation_server_ must not have been set to non NULL.
1198 // (if this regresses, start by looking at LaunchComplete()).
1199 DCHECK(automation_server_
== NULL
);
1201 DCHECK(automation_server_
== NULL
);
1205 void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame(
1206 int selected_command
) {
1208 tab_
->SendContextMenuCommand(selected_command
);
1211 std::wstring
ChromeFrameAutomationClient::GetVersion() const {
1212 return GetCurrentModuleVersion();
1215 void ChromeFrameAutomationClient::Print(HDC print_dc
,
1216 const RECT
& print_bounds
) {
1222 HDC window_dc
= ::GetDC(tab_window_
);
1224 BitBlt(print_dc
, print_bounds
.left
, print_bounds
.top
,
1225 print_bounds
.right
- print_bounds
.left
,
1226 print_bounds
.bottom
- print_bounds
.top
,
1227 window_dc
, print_bounds
.left
, print_bounds
.top
,
1230 ::ReleaseDC(tab_window_
, window_dc
);
1233 void ChromeFrameAutomationClient::PrintTab() {
1238 void ChromeFrameAutomationClient::AttachExternalTab(
1239 uint64 external_tab_cookie
) {
1240 DCHECK_EQ(static_cast<TabProxy
*>(NULL
), tab_
.get());
1241 DCHECK_EQ(-1, tab_handle_
);
1243 external_tab_cookie_
= external_tab_cookie
;
1246 void ChromeFrameAutomationClient::BlockExternalTab(uint64 cookie
) {
1247 // The host does not want this tab to be shown (due popup blocker).
1248 IPC::SyncMessage
* message
=
1249 new AutomationMsg_ConnectExternalTab(cookie
, false, m_hWnd
,
1250 NULL
, NULL
, NULL
, 0);
1251 automation_server_
->SendAsAsync(message
, NULL
, this);
1254 void ChromeFrameAutomationClient::SetPageFontSize(
1255 enum AutomationPageFontSize font_size
) {
1256 if (font_size
< SMALLEST_FONT
||
1257 font_size
> LARGEST_FONT
) {
1258 NOTREACHED() << "Invalid font size specified : "
1263 automation_server_
->Send(
1264 new AutomationMsg_SetPageFontSize(tab_handle_
, font_size
));
1267 void ChromeFrameAutomationClient::RemoveBrowsingData(int remove_mask
) {
1268 automation_server_
->Send(new AutomationMsg_RemoveBrowsingData(remove_mask
));
1271 void ChromeFrameAutomationClient::SetUrlFetcher(
1272 PluginUrlRequestManager
* url_fetcher
) {
1273 DCHECK(url_fetcher
!= NULL
);
1274 url_fetcher_
= url_fetcher
;
1275 url_fetcher_flags_
= url_fetcher
->GetThreadSafeFlags();
1276 url_fetcher_
->set_delegate(this);
1279 void ChromeFrameAutomationClient::SetZoomLevel(content::PageZoom zoom_level
) {
1280 if (automation_server_
) {
1281 automation_server_
->Send(new AutomationMsg_SetZoomLevel(tab_handle_
,
1286 void ChromeFrameAutomationClient::OnUnload(bool* should_unload
) {
1287 *should_unload
= true;
1288 if (automation_server_
) {
1289 const DWORD kUnloadEventTimeout
= 20000;
1291 IPC::SyncMessage
* msg
= new AutomationMsg_RunUnloadHandlers(tab_handle_
,
1293 base::WaitableEvent
unload_call_finished(false, false);
1294 UnloadContext
* unload_context
= new UnloadContext(&unload_call_finished
,
1296 automation_server_
->SendAsAsync(msg
, unload_context
, this);
1297 HANDLE done
= unload_call_finished
.handle();
1298 WaitWithMessageLoop(&done
, 1, kUnloadEventTimeout
);
1302 //////////////////////////////////////////////////////////////////////////
1303 // PluginUrlRequestDelegate implementation.
1304 // Forward network related responses to Chrome.
1306 void ChromeFrameAutomationClient::OnResponseStarted(int request_id
,
1307 const char* mime_type
, const char* headers
, int size
,
1308 base::Time last_modified
, const std::string
& redirect_url
,
1309 int redirect_status
, const net::HostPortPair
& socket_address
) {
1310 AutomationURLResponse response
;
1311 response
.mime_type
= mime_type
;
1313 response
.headers
= headers
;
1314 response
.content_length
= size
;
1315 response
.last_modified
= last_modified
;
1316 response
.redirect_url
= redirect_url
;
1317 response
.redirect_status
= redirect_status
;
1318 response
.socket_address
= socket_address
;
1320 automation_server_
->Send(new AutomationMsg_RequestStarted(
1321 tab_
->handle(), request_id
, response
));
1324 void ChromeFrameAutomationClient::OnReadComplete(int request_id
,
1325 const std::string
& data
) {
1326 automation_server_
->Send(new AutomationMsg_RequestData(
1327 tab_
->handle(), request_id
, data
));
1330 void ChromeFrameAutomationClient::OnResponseEnd(
1332 const net::URLRequestStatus
& status
) {
1333 automation_server_
->Send(new AutomationMsg_RequestEnd(
1334 tab_
->handle(), request_id
, status
));
1337 void ChromeFrameAutomationClient::OnCookiesRetrieved(bool success
,
1338 const GURL
& url
, const std::string
& cookie_string
, int cookie_id
) {
1339 automation_server_
->Send(new AutomationMsg_GetCookiesHostResponse(
1340 tab_
->handle(), success
, url
, cookie_string
, cookie_id
));