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