OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome_frame/chrome_frame_automation.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/callback.h" | |
10 #include "base/command_line.h" | |
11 #include "base/compiler_specific.h" | |
12 #include "base/debug/trace_event.h" | |
13 #include "base/file_version_info.h" | |
14 #include "base/lazy_instance.h" | |
15 #include "base/logging.h" | |
16 #include "base/path_service.h" | |
17 #include "base/process/launch.h" | |
18 #include "base/strings/string_util.h" | |
19 #include "base/strings/utf_string_conversions.h" | |
20 #include "base/synchronization/lock.h" | |
21 #include "base/synchronization/waitable_event.h" | |
22 #include "base/sys_info.h" | |
23 #include "chrome/app/client_util.h" | |
24 #include "chrome/common/automation_messages.h" | |
25 #include "chrome/common/chrome_constants.h" | |
26 #include "chrome/common/chrome_switches.h" | |
27 #include "chrome/test/automation/tab_proxy.h" | |
28 #include "chrome_frame/chrome_launcher_utils.h" | |
29 #include "chrome_frame/crash_reporting/crash_metrics.h" | |
30 #include "chrome_frame/custom_sync_call_context.h" | |
31 #include "chrome_frame/navigation_constraints.h" | |
32 #include "chrome_frame/simple_resource_loader.h" | |
33 #include "chrome_frame/utils.h" | |
34 #include "ui/base/ui_base_switches.h" | |
35 | |
36 namespace { | |
37 | |
38 #ifdef NDEBUG | |
39 int64 kAutomationServerReasonableLaunchDelay = 1000; // in milliseconds | |
40 #else | |
41 int64 kAutomationServerReasonableLaunchDelay = 1000 * 10; | |
42 #endif | |
43 | |
44 } // namespace | |
45 | |
46 class ChromeFrameAutomationProxyImpl::TabProxyNotificationMessageFilter | |
47 : public IPC::ChannelProxy::MessageFilter { | |
48 public: | |
49 explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker) | |
50 : tracker_(tracker) { | |
51 } | |
52 | |
53 void AddTabProxy(AutomationHandle tab_proxy) { | |
54 base::AutoLock lock(lock_); | |
55 tabs_list_.push_back(tab_proxy); | |
56 } | |
57 | |
58 void RemoveTabProxy(AutomationHandle tab_proxy) { | |
59 base::AutoLock lock(lock_); | |
60 tabs_list_.remove(tab_proxy); | |
61 } | |
62 | |
63 virtual bool OnMessageReceived(const IPC::Message& message) { | |
64 if (message.is_reply()) | |
65 return false; | |
66 | |
67 if (!ChromeFrameDelegateImpl::IsTabMessage(message)) | |
68 return false; | |
69 | |
70 // Get AddRef-ed pointer to corresponding TabProxy object | |
71 TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource( | |
72 message.routing_id())); | |
73 bool handled = false; | |
74 if (tab) { | |
75 handled = tab->OnMessageReceived(message); | |
76 tab->Release(); | |
77 } else { | |
78 DLOG(ERROR) << "Failed to find TabProxy for tab:" << message.routing_id(); | |
79 // To prevent subsequent crashes, we set handled to true in this case. | |
80 handled = true; | |
81 } | |
82 return handled; | |
83 } | |
84 | |
85 virtual void OnChannelError() { | |
86 std::list<AutomationHandle>::const_iterator iter = tabs_list_.begin(); | |
87 for (; iter != tabs_list_.end(); ++iter) { | |
88 // Get AddRef-ed pointer to corresponding TabProxy object | |
89 TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(*iter)); | |
90 if (tab) { | |
91 tab->OnChannelError(); | |
92 tab->Release(); | |
93 } | |
94 } | |
95 } | |
96 | |
97 private: | |
98 AutomationHandleTracker* tracker_; | |
99 std::list<AutomationHandle> tabs_list_; | |
100 base::Lock lock_; | |
101 }; | |
102 | |
103 class ChromeFrameAutomationProxyImpl::CFMsgDispatcher | |
104 : public SyncMessageReplyDispatcher { | |
105 public: | |
106 CFMsgDispatcher() : SyncMessageReplyDispatcher() {} | |
107 protected: | |
108 virtual bool HandleMessageType(const IPC::Message& msg, | |
109 SyncMessageCallContext* context) { | |
110 return true; | |
111 } | |
112 }; | |
113 | |
114 ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl( | |
115 AutomationProxyCacheEntry* entry, | |
116 std::string channel_id, base::TimeDelta launch_timeout) | |
117 : AutomationProxy(launch_timeout, false), proxy_entry_(entry) { | |
118 TRACE_EVENT_BEGIN_ETW("chromeframe.automationproxy", this, ""); | |
119 | |
120 InitializeChannel(channel_id, false); | |
121 | |
122 sync_ = new CFMsgDispatcher(); | |
123 message_filter_ = new TabProxyNotificationMessageFilter(tracker_.get()); | |
124 | |
125 // Order of filters is not important. | |
126 channel_->AddFilter(message_filter_.get()); | |
127 channel_->AddFilter(sync_.get()); | |
128 } | |
129 | |
130 ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() { | |
131 TRACE_EVENT_END_ETW("chromeframe.automationproxy", this, ""); | |
132 } | |
133 | |
134 void ChromeFrameAutomationProxyImpl::SendAsAsync( | |
135 IPC::SyncMessage* msg, | |
136 SyncMessageReplyDispatcher::SyncMessageCallContext* context, void* key) { | |
137 sync_->Push(msg, context, key); | |
138 channel_->ChannelProxy::Send(msg); | |
139 } | |
140 | |
141 void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) { | |
142 sync_->Cancel(key); | |
143 } | |
144 | |
145 void ChromeFrameAutomationProxyImpl::OnChannelError() { | |
146 DLOG(ERROR) << "Automation server died"; | |
147 if (proxy_entry_) { | |
148 proxy_entry_->OnChannelError(); | |
149 } else { | |
150 NOTREACHED(); | |
151 } | |
152 } | |
153 | |
154 scoped_refptr<TabProxy> ChromeFrameAutomationProxyImpl::CreateTabProxy( | |
155 int handle) { | |
156 DCHECK(tracker_->GetResource(handle) == NULL); | |
157 TabProxy* tab_proxy = new TabProxy(this, tracker_.get(), handle); | |
158 if (tab_proxy != NULL) | |
159 message_filter_->AddTabProxy(handle); | |
160 return tab_proxy; | |
161 } | |
162 | |
163 void ChromeFrameAutomationProxyImpl::ReleaseTabProxy(AutomationHandle handle) { | |
164 message_filter_->RemoveTabProxy(handle); | |
165 } | |
166 | |
167 struct LaunchTimeStats { | |
168 #ifndef NDEBUG | |
169 LaunchTimeStats() { | |
170 launch_time_begin_ = base::Time::Now(); | |
171 } | |
172 | |
173 void Dump() { | |
174 base::TimeDelta launch_time = base::Time::Now() - launch_time_begin_; | |
175 UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchTime", launch_time); | |
176 const int64 launch_milliseconds = launch_time.InMilliseconds(); | |
177 if (launch_milliseconds > kAutomationServerReasonableLaunchDelay) { | |
178 LOG(WARNING) << "Automation server launch took longer than expected: " << | |
179 launch_milliseconds << " ms."; | |
180 } | |
181 } | |
182 | |
183 base::Time launch_time_begin_; | |
184 #else | |
185 void Dump() {} | |
186 #endif | |
187 }; | |
188 | |
189 AutomationProxyCacheEntry::AutomationProxyCacheEntry( | |
190 ChromeFrameLaunchParams* params, LaunchDelegate* delegate) | |
191 : profile_name(params->profile_name()), | |
192 launch_result_(AUTOMATION_LAUNCH_RESULT_INVALID) { | |
193 DCHECK(delegate); | |
194 thread_.reset(new base::Thread(WideToASCII(profile_name).c_str())); | |
195 thread_->Start(); | |
196 // Use scoped_refptr so that the params will get released when the task | |
197 // has been run. | |
198 scoped_refptr<ChromeFrameLaunchParams> ref_params(params); | |
199 thread_->message_loop()->PostTask( | |
200 FROM_HERE, base::Bind(&AutomationProxyCacheEntry::CreateProxy, | |
201 base::Unretained(this), ref_params, delegate)); | |
202 } | |
203 | |
204 AutomationProxyCacheEntry::~AutomationProxyCacheEntry() { | |
205 DVLOG(1) << __FUNCTION__ << profile_name; | |
206 // Attempt to fix chrome_frame_tests crash seen at times on the IE6/IE7 | |
207 // builders. It appears that there are cases when we can enter here when the | |
208 // AtExitManager is tearing down the global ProxyCache which causes a crash | |
209 // while tearing down the AutomationProxy object due to a NULL MessageLoop | |
210 // The AutomationProxy class uses the SyncChannel which assumes the existence | |
211 // of a MessageLoop instance. | |
212 // We leak the AutomationProxy pointer here to avoid a crash. | |
213 if (base::MessageLoop::current() == NULL) { | |
214 proxy_.release(); | |
215 } | |
216 } | |
217 | |
218 void AutomationProxyCacheEntry::CreateProxy(ChromeFrameLaunchParams* params, | |
219 LaunchDelegate* delegate) { | |
220 DCHECK(IsSameThread(base::PlatformThread::CurrentId())); | |
221 DCHECK(delegate); | |
222 DCHECK(params); | |
223 DCHECK(proxy_.get() == NULL); | |
224 | |
225 // We *must* create automationproxy in a thread that has message loop, | |
226 // since SyncChannel::Context construction registers event to be watched | |
227 // through ObjectWatcher which subscribes for the current thread message loop | |
228 // destruction notification. | |
229 | |
230 // At same time we must destroy/stop the thread from another thread. | |
231 std::string channel_id = AutomationProxy::GenerateChannelID(); | |
232 ChromeFrameAutomationProxyImpl* proxy = | |
233 new ChromeFrameAutomationProxyImpl( | |
234 this, | |
235 channel_id, | |
236 base::TimeDelta::FromMilliseconds(params->launch_timeout())); | |
237 | |
238 // Ensure that the automation proxy actually respects our choice on whether | |
239 // or not to check the version. | |
240 proxy->set_perform_version_check(params->version_check()); | |
241 | |
242 // Launch browser | |
243 std::wstring command_line_string; | |
244 scoped_ptr<CommandLine> command_line; | |
245 if (chrome_launcher::CreateLaunchCommandLine(&command_line)) { | |
246 command_line->AppendSwitchASCII(switches::kAutomationClientChannelID, | |
247 channel_id); | |
248 | |
249 // Run Chrome in Chrome Frame mode. In practice, this modifies the paths | |
250 // and registry keys that Chrome looks in via the BrowserDistribution | |
251 // mechanism. | |
252 command_line->AppendSwitch(switches::kChromeFrame); | |
253 | |
254 // Chrome Frame never wants Chrome to start up with a First Run UI. | |
255 command_line->AppendSwitch(switches::kNoFirstRun); | |
256 | |
257 // Chrome Frame never wants to run background extensions since they | |
258 // interfere with in-use updates. | |
259 command_line->AppendSwitch(switches::kDisableBackgroundMode); | |
260 | |
261 command_line->AppendSwitch(switches::kDisablePopupBlocking); | |
262 | |
263 #if defined(GOOGLE_CHROME_BUILD) | |
264 // Chrome Frame should use the native print dialog. | |
265 command_line->AppendSwitch(switches::kDisablePrintPreview); | |
266 #endif | |
267 | |
268 // Disable the "Whoa! Chrome has crashed." dialog, because that isn't very | |
269 // useful for Chrome Frame users. | |
270 #ifndef NDEBUG | |
271 command_line->AppendSwitch(switches::kNoErrorDialogs); | |
272 #endif | |
273 | |
274 // In headless mode runs like reliability test runs we want full crash dumps | |
275 // from chrome. | |
276 if (IsHeadlessMode()) | |
277 command_line->AppendSwitch(switches::kFullMemoryCrashReport); | |
278 | |
279 // In accessible mode automation tests expect renderer accessibility to be | |
280 // enabled in chrome. | |
281 if (IsAccessibleMode()) | |
282 command_line->AppendSwitch(switches::kForceRendererAccessibility); | |
283 | |
284 DVLOG(1) << "Profile path: " << params->profile_path().value(); | |
285 command_line->AppendSwitchPath(switches::kUserDataDir, | |
286 params->profile_path()); | |
287 | |
288 // Ensure that Chrome is running the specified version of chrome.dll. | |
289 command_line->AppendSwitchNative(switches::kChromeVersion, | |
290 GetCurrentModuleVersion()); | |
291 | |
292 if (!params->language().empty()) | |
293 command_line->AppendSwitchNative(switches::kLang, params->language()); | |
294 | |
295 command_line_string = command_line->GetCommandLineString(); | |
296 } | |
297 | |
298 automation_server_launch_start_time_ = base::TimeTicks::Now(); | |
299 | |
300 if (command_line_string.empty() || | |
301 !base::LaunchProcess(command_line_string, base::LaunchOptions(), NULL)) { | |
302 // We have no code for launch failure. | |
303 launch_result_ = AUTOMATION_LAUNCH_RESULT_INVALID; | |
304 } else { | |
305 // Launch timeout may happen if the new instance tries to communicate | |
306 // with an existing Chrome instance that is hung and displays msgbox | |
307 // asking to kill the previous one. This could be easily observed if the | |
308 // already running Chrome instance is running as high-integrity process | |
309 // (started with "Run as Administrator" or launched by another high | |
310 // integrity process) hence our medium-integrity process | |
311 // cannot SendMessage to it with request to activate itself. | |
312 | |
313 // TODO(stoyan) AutomationProxy eats Hello message, hence installing | |
314 // message filter is pointless, we can leverage ObjectWatcher and use | |
315 // system thread pool to notify us when proxy->AppLaunch event is signaled. | |
316 LaunchTimeStats launch_stats; | |
317 // Wait for the automation server launch result, then stash away the | |
318 // version string it reported. | |
319 launch_result_ = proxy->WaitForAppLaunch(); | |
320 launch_stats.Dump(); | |
321 | |
322 base::TimeDelta delta = | |
323 base::TimeTicks::Now() - automation_server_launch_start_time_; | |
324 | |
325 if (launch_result_ == AUTOMATION_SUCCESS) { | |
326 UMA_HISTOGRAM_TIMES( | |
327 "ChromeFrame.AutomationServerLaunchSuccessTime", delta); | |
328 } else { | |
329 UMA_HISTOGRAM_TIMES( | |
330 "ChromeFrame.AutomationServerLaunchFailedTime", delta); | |
331 } | |
332 | |
333 UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.LaunchResult", | |
334 launch_result_, | |
335 AUTOMATION_SUCCESS, | |
336 AUTOMATION_CREATE_TAB_FAILED, | |
337 AUTOMATION_CREATE_TAB_FAILED + 1); | |
338 } | |
339 | |
340 TRACE_EVENT_END_ETW("chromeframe.createproxy", this, ""); | |
341 | |
342 // Finally set the proxy. | |
343 proxy_.reset(proxy); | |
344 launch_delegates_.push_back(delegate); | |
345 | |
346 delegate->LaunchComplete(proxy_.get(), launch_result_); | |
347 } | |
348 | |
349 void AutomationProxyCacheEntry::RemoveDelegate(LaunchDelegate* delegate, | |
350 base::WaitableEvent* done, | |
351 bool* was_last_delegate) { | |
352 DCHECK(IsSameThread(base::PlatformThread::CurrentId())); | |
353 DCHECK(delegate); | |
354 DCHECK(done); | |
355 DCHECK(was_last_delegate); | |
356 | |
357 *was_last_delegate = false; | |
358 | |
359 LaunchDelegates::iterator it = std::find(launch_delegates_.begin(), | |
360 launch_delegates_.end(), delegate); | |
361 if (it == launch_delegates_.end()) { | |
362 NOTREACHED(); | |
363 } else { | |
364 if (launch_delegates_.size() == 1) { | |
365 *was_last_delegate = true; | |
366 | |
367 // Process pending notifications. | |
368 thread_->message_loop()->RunUntilIdle(); | |
369 | |
370 // Take down the proxy since we no longer have any clients. | |
371 // Make sure we only do this once all pending messages have been cleared. | |
372 proxy_.reset(NULL); | |
373 } | |
374 // Be careful to remove from the list after running pending | |
375 // tasks. Otherwise the delegate being removed might miss out | |
376 // on pending notifications such as LaunchComplete. | |
377 launch_delegates_.erase(it); | |
378 } | |
379 | |
380 done->Signal(); | |
381 } | |
382 | |
383 void AutomationProxyCacheEntry::AddDelegate(LaunchDelegate* delegate) { | |
384 DCHECK(IsSameThread(base::PlatformThread::CurrentId())); | |
385 DCHECK(std::find(launch_delegates_.begin(), | |
386 launch_delegates_.end(), | |
387 delegate) == launch_delegates_.end()) | |
388 << "Same delegate being added twice"; | |
389 DCHECK(launch_result_ != AUTOMATION_LAUNCH_RESULT_INVALID); | |
390 | |
391 launch_delegates_.push_back(delegate); | |
392 delegate->LaunchComplete(proxy_.get(), launch_result_); | |
393 } | |
394 | |
395 void AutomationProxyCacheEntry::OnChannelError() { | |
396 DCHECK(IsSameThread(base::PlatformThread::CurrentId())); | |
397 launch_result_ = AUTOMATION_SERVER_CRASHED; | |
398 LaunchDelegates::const_iterator it = launch_delegates_.begin(); | |
399 for (; it != launch_delegates_.end(); ++it) { | |
400 (*it)->AutomationServerDied(); | |
401 } | |
402 } | |
403 | |
404 ProxyFactory::ProxyFactory() { | |
405 } | |
406 | |
407 ProxyFactory::~ProxyFactory() { | |
408 for (size_t i = 0; i < proxies_.container().size(); ++i) { | |
409 DWORD result = proxies_[i]->WaitForThread(0); | |
410 if (WAIT_OBJECT_0 != result) | |
411 // TODO(stoyan): Don't leak proxies on exit. | |
412 DLOG(ERROR) << "Proxies leaked on exit."; | |
413 } | |
414 } | |
415 | |
416 void ProxyFactory::GetAutomationServer( | |
417 LaunchDelegate* delegate, ChromeFrameLaunchParams* params, | |
418 void** automation_server_id) { | |
419 TRACE_EVENT_BEGIN_ETW("chromeframe.createproxy", this, ""); | |
420 | |
421 scoped_refptr<AutomationProxyCacheEntry> entry; | |
422 // Find already existing launcher thread for given profile | |
423 base::AutoLock lock(lock_); | |
424 for (size_t i = 0; i < proxies_.container().size(); ++i) { | |
425 if (proxies_[i]->IsSameProfile(params->profile_name())) { | |
426 entry = proxies_[i]; | |
427 break; | |
428 } | |
429 } | |
430 | |
431 if (entry == NULL) { | |
432 DVLOG(1) << __FUNCTION__ << " creating new proxy entry"; | |
433 entry = new AutomationProxyCacheEntry(params, delegate); | |
434 proxies_.container().push_back(entry); | |
435 } else if (delegate) { | |
436 // Notify the new delegate of the launch status from the worker thread | |
437 // and add it to the list of delegates. | |
438 entry->message_loop()->PostTask( | |
439 FROM_HERE, base::Bind(&AutomationProxyCacheEntry::AddDelegate, | |
440 base::Unretained(entry.get()), delegate)); | |
441 } | |
442 | |
443 DCHECK(automation_server_id != NULL); | |
444 DCHECK(!entry->IsSameThread(base::PlatformThread::CurrentId())); | |
445 | |
446 *automation_server_id = entry; | |
447 } | |
448 | |
449 bool ProxyFactory::ReleaseAutomationServer(void* server_id, | |
450 LaunchDelegate* delegate) { | |
451 if (!server_id) { | |
452 NOTREACHED(); | |
453 return false; | |
454 } | |
455 | |
456 AutomationProxyCacheEntry* entry = | |
457 reinterpret_cast<AutomationProxyCacheEntry*>(server_id); | |
458 | |
459 #ifndef NDEBUG | |
460 lock_.Acquire(); | |
461 Vector::ContainerType::iterator it = std::find(proxies_.container().begin(), | |
462 proxies_.container().end(), | |
463 entry); | |
464 DCHECK(it != proxies_.container().end()); | |
465 DCHECK(!entry->IsSameThread(base::PlatformThread::CurrentId())); | |
466 | |
467 lock_.Release(); | |
468 #endif | |
469 | |
470 // AddRef the entry object as we might need to take it out of the proxy | |
471 // stack and then uninitialize the entry. | |
472 entry->AddRef(); | |
473 | |
474 bool last_delegate = false; | |
475 if (delegate) { | |
476 base::WaitableEvent done(true, false); | |
477 entry->message_loop()->PostTask( | |
478 FROM_HERE, | |
479 base::Bind(&AutomationProxyCacheEntry::RemoveDelegate, | |
480 base::Unretained(entry), delegate, &done, &last_delegate)); | |
481 done.Wait(); | |
482 } | |
483 | |
484 if (last_delegate) { | |
485 lock_.Acquire(); | |
486 Vector::ContainerType::iterator it = std::find(proxies_.container().begin(), | |
487 proxies_.container().end(), | |
488 entry); | |
489 if (it != proxies_.container().end()) { | |
490 proxies_.container().erase(it); | |
491 } else { | |
492 DLOG(ERROR) << "Proxy wasn't found. Proxy map is likely empty (size=" | |
493 << proxies_.container().size() << ")."; | |
494 } | |
495 | |
496 lock_.Release(); | |
497 } | |
498 | |
499 entry->Release(); | |
500 | |
501 return true; | |
502 } | |
503 | |
504 static base::LazyInstance<ProxyFactory>::Leaky | |
505 g_proxy_factory = LAZY_INSTANCE_INITIALIZER; | |
506 | |
507 ChromeFrameAutomationClient::ChromeFrameAutomationClient() | |
508 : chrome_frame_delegate_(NULL), | |
509 chrome_window_(NULL), | |
510 tab_window_(NULL), | |
511 parent_window_(NULL), | |
512 automation_server_(NULL), | |
513 automation_server_id_(NULL), | |
514 ui_thread_id_(NULL), | |
515 init_state_(UNINITIALIZED), | |
516 use_chrome_network_(false), | |
517 proxy_factory_(g_proxy_factory.Pointer()), | |
518 handle_top_level_requests_(false), | |
519 tab_handle_(-1), | |
520 session_id_(-1), | |
521 url_fetcher_(NULL), | |
522 url_fetcher_flags_(PluginUrlRequestManager::NOT_THREADSAFE), | |
523 navigate_after_initialization_(false), | |
524 route_all_top_level_navigations_(false) { | |
525 } | |
526 | |
527 ChromeFrameAutomationClient::~ChromeFrameAutomationClient() { | |
528 // Uninitialize must be called prior to the destructor | |
529 DCHECK(automation_server_ == NULL); | |
530 } | |
531 | |
532 bool ChromeFrameAutomationClient::Initialize( | |
533 ChromeFrameDelegate* chrome_frame_delegate, | |
534 ChromeFrameLaunchParams* chrome_launch_params) { | |
535 DCHECK(!IsWindow()); | |
536 chrome_frame_delegate_ = chrome_frame_delegate; | |
537 | |
538 #ifndef NDEBUG | |
539 if (chrome_launch_params_ && chrome_launch_params_ != chrome_launch_params) { | |
540 DCHECK_EQ(chrome_launch_params_->url(), chrome_launch_params->url()); | |
541 DCHECK_EQ(chrome_launch_params_->referrer(), | |
542 chrome_launch_params->referrer()); | |
543 } | |
544 #endif | |
545 | |
546 chrome_launch_params_ = chrome_launch_params; | |
547 | |
548 ui_thread_id_ = base::PlatformThread::CurrentId(); | |
549 #ifndef NDEBUG | |
550 // In debug mode give more time to work with a debugger. | |
551 if (IsDebuggerPresent()) { | |
552 // Don't use INFINITE (which is -1) or even MAXINT since we will convert | |
553 // from milliseconds to microseconds when stored in a base::TimeDelta, | |
554 // thus * 1000. An hour should be enough. | |
555 chrome_launch_params_->set_launch_timeout(60 * 60 * 1000); | |
556 } else { | |
557 DCHECK_LT(chrome_launch_params_->launch_timeout(), | |
558 MAXINT / 2000); | |
559 chrome_launch_params_->set_launch_timeout( | |
560 chrome_launch_params_->launch_timeout() * 2); | |
561 } | |
562 #endif // NDEBUG | |
563 | |
564 // Create a window on the UI thread for marshaling messages back and forth | |
565 // from the IPC thread. This window cannot be a message only window as the | |
566 // external chrome tab window is created as a child of this window. This | |
567 // window is eventually reparented to the ActiveX plugin window. | |
568 if (!Create(GetDesktopWindow(), NULL, NULL, | |
569 WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, | |
570 WS_EX_TOOLWINDOW)) { | |
571 NOTREACHED(); | |
572 return false; | |
573 } | |
574 | |
575 // Keep object in memory, while the window is alive. | |
576 // Corresponding Release is in OnFinalMessage(); | |
577 AddRef(); | |
578 | |
579 // Mark our state as initializing. We'll reach initialized once | |
580 // InitializeComplete is called successfully. | |
581 init_state_ = INITIALIZING; | |
582 | |
583 HRESULT hr = S_OK; | |
584 | |
585 if (chrome_launch_params_->url().is_valid()) | |
586 navigate_after_initialization_ = false; | |
587 | |
588 proxy_factory_->GetAutomationServer(static_cast<LaunchDelegate*>(this), | |
589 chrome_launch_params_, &automation_server_id_); | |
590 | |
591 return true; | |
592 } | |
593 | |
594 void ChromeFrameAutomationClient::Uninitialize() { | |
595 if (init_state_ == UNINITIALIZED) { | |
596 DLOG(WARNING) << __FUNCTION__ << ": Automation client not initialized"; | |
597 return; | |
598 } | |
599 | |
600 init_state_ = UNINITIALIZING; | |
601 | |
602 // Called from client's FinalRelease() / destructor | |
603 if (url_fetcher_) { | |
604 url_fetcher_ = NULL; | |
605 } | |
606 | |
607 if (tab_) { | |
608 tab_->RemoveObserver(this); | |
609 if (automation_server_) | |
610 automation_server_->ReleaseTabProxy(tab_->handle()); | |
611 tab_ = NULL; // scoped_refptr::Release | |
612 } | |
613 | |
614 // Wait for the automation proxy's worker thread to exit. | |
615 ReleaseAutomationServer(); | |
616 | |
617 // We must destroy the window, since if there are pending tasks | |
618 // window procedure may be invoked after DLL is unloaded. | |
619 // Unfortunately pending tasks are leaked. | |
620 if (::IsWindow(m_hWnd)) | |
621 DestroyWindow(); | |
622 | |
623 // DCHECK(navigate_after_initialization_ == false); | |
624 handle_top_level_requests_ = false; | |
625 ui_thread_id_ = 0; | |
626 chrome_frame_delegate_ = NULL; | |
627 init_state_ = UNINITIALIZED; | |
628 } | |
629 | |
630 bool ChromeFrameAutomationClient::InitiateNavigation( | |
631 const std::string& url, | |
632 const std::string& referrer, | |
633 NavigationConstraints* navigation_constraints) { | |
634 return true; | |
635 } | |
636 | |
637 void ChromeFrameAutomationClient::BeginNavigateCompleted( | |
638 AutomationMsg_NavigationResponseValues result) { | |
639 if (result == AUTOMATION_MSG_NAVIGATION_ERROR) | |
640 ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, | |
641 chrome_launch_params_->url().spec()); | |
642 } | |
643 | |
644 void ChromeFrameAutomationClient::FindInPage(const std::wstring& search_string, | |
645 FindInPageDirection forward, | |
646 FindInPageCase match_case, | |
647 bool find_next) { | |
648 // Note that we can be called by the find dialog after the tab has gone away. | |
649 if (!tab_) | |
650 return; | |
651 | |
652 // What follows is quite similar to TabProxy::FindInPage() but uses | |
653 // the SyncMessageReplyDispatcher to avoid concerns about blocking | |
654 // synchronous messages. | |
655 AutomationMsg_Find_Params params; | |
656 params.search_string = base::WideToUTF16Hack(search_string); | |
657 params.find_next = find_next; | |
658 params.match_case = (match_case == CASE_SENSITIVE); | |
659 params.forward = (forward == FWD); | |
660 | |
661 IPC::SyncMessage* msg = | |
662 new AutomationMsg_Find(tab_->handle(), params, NULL, NULL); | |
663 automation_server_->SendAsAsync(msg, NULL, this); | |
664 } | |
665 | |
666 // Invoked in the automation proxy's worker thread. | |
667 void ChromeFrameAutomationClient::LaunchComplete( | |
668 ChromeFrameAutomationProxy* proxy, | |
669 AutomationLaunchResult result) { | |
670 } | |
671 | |
672 // Invoked in the automation proxy's worker thread. | |
673 void ChromeFrameAutomationClient::AutomationServerDied() { | |
674 // Then uninitialize. | |
675 PostTask( | |
676 FROM_HERE, base::Bind(&ChromeFrameAutomationClient::Uninitialize, | |
677 base::Unretained(this))); | |
678 } | |
679 | |
680 // These are invoked in channel's background thread. | |
681 // Cannot call any method of the activex here since it is a STA kind of being. | |
682 // By default we marshal the IPC message to the main/GUI thread and from there | |
683 // we safely invoke chrome_frame_delegate_->OnMessageReceived(msg). | |
684 bool ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab, | |
685 const IPC::Message& msg) { | |
686 DCHECK(tab == tab_.get()); | |
687 | |
688 // Early check to avoid needless marshaling | |
689 if (chrome_frame_delegate_ == NULL) | |
690 return false; | |
691 | |
692 PostTask(FROM_HERE, | |
693 base::Bind(&ChromeFrameAutomationClient::OnMessageReceivedUIThread, | |
694 base::Unretained(this), msg)); | |
695 return true; | |
696 } | |
697 | |
698 void ChromeFrameAutomationClient::OnChannelError(TabProxy* tab) { | |
699 DCHECK(tab == tab_.get()); | |
700 // Early check to avoid needless marshaling | |
701 if (chrome_frame_delegate_ == NULL) | |
702 return; | |
703 | |
704 PostTask( | |
705 FROM_HERE, | |
706 base::Bind(&ChromeFrameAutomationClient::OnChannelErrorUIThread, | |
707 base::Unretained(this))); | |
708 } | |
709 | |
710 void ChromeFrameAutomationClient::OnMessageReceivedUIThread( | |
711 const IPC::Message& msg) { | |
712 DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_); | |
713 // Forward to the delegate. | |
714 if (chrome_frame_delegate_) | |
715 chrome_frame_delegate_->OnMessageReceived(msg); | |
716 } | |
717 | |
718 void ChromeFrameAutomationClient::OnChannelErrorUIThread() { | |
719 DCHECK_EQ(base::PlatformThread::CurrentId(), ui_thread_id_); | |
720 | |
721 // Report a metric that something went wrong unexpectedly. | |
722 CrashMetricsReporter::GetInstance()->IncrementMetric( | |
723 CrashMetricsReporter::CHANNEL_ERROR_COUNT); | |
724 | |
725 // Forward to the delegate. | |
726 if (chrome_frame_delegate_) | |
727 chrome_frame_delegate_->OnChannelError(); | |
728 } | |
729 | |
730 void ChromeFrameAutomationClient::ReportNavigationError( | |
731 AutomationMsg_NavigationResponseValues error_code, | |
732 const std::string& url) { | |
733 if (!chrome_frame_delegate_) | |
734 return; | |
735 | |
736 if (ui_thread_id_ == base::PlatformThread::CurrentId()) { | |
737 chrome_frame_delegate_->OnLoadFailed(error_code, url); | |
738 } else { | |
739 PostTask(FROM_HERE, | |
740 base::Bind(&ChromeFrameAutomationClient::ReportNavigationError, | |
741 base::Unretained(this), error_code, url)); | |
742 } | |
743 } | |
744 | |
745 void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window) { | |
746 parent_window_ = parent_window; | |
747 // If we're done with the initialization step, go ahead | |
748 if (is_initialized()) { | |
749 if (parent_window == NULL) { | |
750 // Hide and reparent the automation window. This window will get | |
751 // reparented to the new ActiveX/Active document window when it gets | |
752 // created. | |
753 ShowWindow(SW_HIDE); | |
754 SetParent(GetDesktopWindow()); | |
755 } else { | |
756 if (!::IsWindow(chrome_window())) { | |
757 DLOG(WARNING) << "Invalid Chrome Window handle in SetParentWindow"; | |
758 return; | |
759 } | |
760 | |
761 if (!SetParent(parent_window)) { | |
762 DLOG(WARNING) << "Failed to set parent window for automation window. " | |
763 << "Error = " | |
764 << GetLastError(); | |
765 return; | |
766 } | |
767 | |
768 RECT parent_client_rect = {0}; | |
769 ::GetClientRect(parent_window, &parent_client_rect); | |
770 int width = parent_client_rect.right - parent_client_rect.left; | |
771 int height = parent_client_rect.bottom - parent_client_rect.top; | |
772 } | |
773 } | |
774 } | |
775 | |
776 void ChromeFrameAutomationClient::ReleaseAutomationServer() { | |
777 if (automation_server_id_) { | |
778 // Cache the server id and clear the automation_server_id_ before | |
779 // calling ReleaseAutomationServer. The reason we do this is that | |
780 // we must cancel pending messages before we release the automation server. | |
781 // Furthermore, while ReleaseAutomationServer is running, we could get | |
782 // a callback to LaunchComplete which could cause an external tab to be | |
783 // created. Ideally the callbacks should be dropped. | |
784 // TODO(ananta) | |
785 // Refactor the ChromeFrameAutomationProxy code to not depend on | |
786 // AutomationProxy and simplify the whole mess. | |
787 void* server_id = automation_server_id_; | |
788 automation_server_id_ = NULL; | |
789 | |
790 if (automation_server_) { | |
791 // Make sure to clean up any pending sync messages before we go away. | |
792 automation_server_->CancelAsync(this); | |
793 } | |
794 | |
795 proxy_factory_->ReleaseAutomationServer(server_id, this); | |
796 automation_server_ = NULL; | |
797 | |
798 // automation_server_ must not have been set to non NULL. | |
799 // (if this regresses, start by looking at LaunchComplete()). | |
800 DCHECK(automation_server_ == NULL); | |
801 } else { | |
802 DCHECK(automation_server_ == NULL); | |
803 } | |
804 } | |
805 | |
806 std::wstring ChromeFrameAutomationClient::GetVersion() const { | |
807 return GetCurrentModuleVersion(); | |
808 } | |
809 | |
810 void ChromeFrameAutomationClient::Print(HDC print_dc, | |
811 const RECT& print_bounds) { | |
812 if (!tab_window_) { | |
813 NOTREACHED(); | |
814 return; | |
815 } | |
816 | |
817 HDC window_dc = ::GetDC(tab_window_); | |
818 | |
819 BitBlt(print_dc, print_bounds.left, print_bounds.top, | |
820 print_bounds.right - print_bounds.left, | |
821 print_bounds.bottom - print_bounds.top, | |
822 window_dc, print_bounds.left, print_bounds.top, | |
823 SRCCOPY); | |
824 | |
825 ::ReleaseDC(tab_window_, window_dc); | |
826 } | |
827 | |
828 void ChromeFrameAutomationClient::SetPageFontSize( | |
829 enum AutomationPageFontSize font_size) { | |
830 if (font_size < SMALLEST_FONT || | |
831 font_size > LARGEST_FONT) { | |
832 NOTREACHED() << "Invalid font size specified : " | |
833 << font_size; | |
834 return; | |
835 } | |
836 | |
837 automation_server_->Send( | |
838 new AutomationMsg_SetPageFontSize(tab_handle_, font_size)); | |
839 } | |
840 | |
841 void ChromeFrameAutomationClient::SetUrlFetcher( | |
842 PluginUrlRequestManager* url_fetcher) { | |
843 DCHECK(url_fetcher != NULL); | |
844 url_fetcher_ = url_fetcher; | |
845 url_fetcher_flags_ = url_fetcher->GetThreadSafeFlags(); | |
846 url_fetcher_->set_delegate(this); | |
847 } | |
OLD | NEW |