Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / automation / automation_provider.cc
blobb8c0a0b9a141953aa98669beac546dbb7bf59cae
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/browser/automation/automation_provider.h"
7 #include <set>
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/command_line.h"
12 #include "base/debug/trace_event.h"
13 #include "base/files/file_path.h"
14 #include "base/json/json_reader.h"
15 #include "base/json/json_string_value_serializer.h"
16 #include "base/json/json_writer.h"
17 #include "base/json/string_escape.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/path_service.h"
20 #include "base/prefs/pref_service.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/synchronization/waitable_event.h"
26 #include "base/threading/thread.h"
27 #include "base/values.h"
28 #include "chrome/app/chrome_command_ids.h"
29 #include "chrome/browser/automation/automation_browser_tracker.h"
30 #include "chrome/browser/automation/automation_provider_list.h"
31 #include "chrome/browser/automation/automation_provider_observers.h"
32 #include "chrome/browser/automation/automation_tab_tracker.h"
33 #include "chrome/browser/automation/automation_window_tracker.h"
34 #include "chrome/browser/browser_process.h"
35 #include "chrome/browser/browsing_data/browsing_data_helper.h"
36 #include "chrome/browser/browsing_data/browsing_data_remover.h"
37 #include "chrome/browser/character_encoding.h"
38 #include "chrome/browser/content_settings/host_content_settings_map.h"
39 #include "chrome/browser/net/url_request_mock_util.h"
40 #include "chrome/browser/printing/print_job.h"
41 #include "chrome/browser/profiles/profile_manager.h"
42 #include "chrome/browser/ssl/ssl_blocking_page.h"
43 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog.h"
44 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
45 #include "chrome/browser/ui/browser_commands.h"
46 #include "chrome/browser/ui/browser_finder.h"
47 #include "chrome/browser/ui/browser_tabstrip.h"
48 #include "chrome/browser/ui/browser_window.h"
49 #include "chrome/browser/ui/find_bar/find_bar.h"
50 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
51 #include "chrome/browser/ui/find_bar/find_notification_details.h"
52 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
53 #include "chrome/browser/ui/login/login_prompt.h"
54 #include "chrome/browser/ui/omnibox/location_bar.h"
55 #include "chrome/common/automation_constants.h"
56 #include "chrome/common/automation_messages.h"
57 #include "chrome/common/chrome_constants.h"
58 #include "chrome/common/chrome_paths.h"
59 #include "chrome/common/chrome_switches.h"
60 #include "chrome/common/chrome_version_info.h"
61 #include "chrome/common/pref_names.h"
62 #include "chrome/common/render_messages.h"
63 #include "chrome/common/url_constants.h"
64 #include "content/public/browser/browser_thread.h"
65 #include "content/public/browser/download_item.h"
66 #include "content/public/browser/native_web_keyboard_event.h"
67 #include "content/public/browser/render_view_host.h"
68 #include "content/public/browser/tracing_controller.h"
69 #include "content/public/browser/web_contents.h"
70 #include "content/public/browser/web_contents_view.h"
71 #include "ipc/ipc_channel_proxy.h"
72 #include "net/proxy/proxy_config_service_fixed.h"
73 #include "net/proxy/proxy_service.h"
74 #include "net/url_request/url_request_context.h"
75 #include "net/url_request/url_request_context_getter.h"
76 #include "third_party/WebKit/public/web/WebFindOptions.h"
78 #if defined(OS_CHROMEOS)
79 #include "chromeos/chromeos_switches.h"
80 #include "chromeos/login/login_state.h"
81 #endif // defined(OS_CHROMEOS)
83 using blink::WebFindOptions;
84 using base::Time;
85 using content::BrowserThread;
86 using content::DownloadItem;
87 using content::NavigationController;
88 using content::RenderViewHost;
89 using content::TracingController;
90 using content::WebContents;
92 AutomationProvider::AutomationProvider(Profile* profile)
93 : profile_(profile),
94 reply_message_(NULL),
95 reinitialize_on_channel_error_(
96 CommandLine::ForCurrentProcess()->HasSwitch(
97 switches::kAutomationReinitializeOnChannelError)),
98 use_initial_load_observers_(true),
99 is_connected_(false),
100 initial_tab_loads_complete_(false),
101 login_webui_ready_(true) {
102 TRACE_EVENT_BEGIN_ETW("AutomationProvider::AutomationProvider", 0, "");
104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
106 browser_tracker_.reset(new AutomationBrowserTracker(this));
107 tab_tracker_.reset(new AutomationTabTracker(this));
108 window_tracker_.reset(new AutomationWindowTracker(this));
109 metric_event_duration_observer_.reset(new MetricEventDurationObserver());
111 TRACE_EVENT_END_ETW("AutomationProvider::AutomationProvider", 0, "");
114 AutomationProvider::~AutomationProvider() {
115 if (channel_.get())
116 channel_->Close();
119 void AutomationProvider::set_profile(Profile* profile) {
120 profile_ = profile;
123 bool AutomationProvider::InitializeChannel(const std::string& channel_id) {
124 TRACE_EVENT_BEGIN_ETW("AutomationProvider::InitializeChannel", 0, "");
126 channel_id_ = channel_id;
127 std::string effective_channel_id = channel_id;
129 // If the channel_id starts with kNamedInterfacePrefix, create a named IPC
130 // server and listen on it, else connect as client to an existing IPC server
131 bool use_named_interface =
132 channel_id.find(automation::kNamedInterfacePrefix) == 0;
133 if (use_named_interface) {
134 effective_channel_id = channel_id.substr(
135 strlen(automation::kNamedInterfacePrefix));
136 if (effective_channel_id.length() <= 0)
137 return false;
139 reinitialize_on_channel_error_ = true;
142 channel_.reset(new IPC::ChannelProxy(
143 effective_channel_id,
144 GetChannelMode(use_named_interface),
145 this,
146 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get()));
148 #if defined(OS_CHROMEOS)
149 if (use_initial_load_observers_) {
150 // Wait for webui login to be ready.
151 // Observer will delete itself.
152 if (CommandLine::ForCurrentProcess()->HasSwitch(
153 chromeos::switches::kLoginManager) &&
154 !chromeos::LoginState::Get()->IsUserLoggedIn()) {
155 login_webui_ready_ = false;
156 new OOBEWebuiReadyObserver(this);
159 #endif
161 TRACE_EVENT_END_ETW("AutomationProvider::InitializeChannel", 0, "");
163 return true;
166 IPC::Channel::Mode AutomationProvider::GetChannelMode(
167 bool use_named_interface) {
168 if (use_named_interface)
169 return IPC::Channel::MODE_NAMED_SERVER;
170 else
171 return IPC::Channel::MODE_CLIENT;
174 std::string AutomationProvider::GetProtocolVersion() {
175 chrome::VersionInfo version_info;
176 return version_info.Version().c_str();
179 void AutomationProvider::SetExpectedTabCount(size_t expected_tabs) {
180 VLOG(2) << "SetExpectedTabCount:" << expected_tabs;
181 if (expected_tabs == 0)
182 OnInitialTabLoadsComplete();
183 else
184 initial_load_observer_.reset(new InitialLoadObserver(expected_tabs, this));
187 void AutomationProvider::OnInitialTabLoadsComplete() {
188 initial_tab_loads_complete_ = true;
189 VLOG(2) << "OnInitialTabLoadsComplete";
190 SendInitialLoadMessage();
193 void AutomationProvider::OnOOBEWebuiReady() {
194 login_webui_ready_ = true;
195 VLOG(2) << "OnOOBEWebuiReady";
196 SendInitialLoadMessage();
199 void AutomationProvider::SendInitialLoadMessage() {
200 if (is_connected_ && initial_tab_loads_complete_ && login_webui_ready_) {
201 VLOG(2) << "Initial loads complete; sending initial loads message.";
202 Send(new AutomationMsg_InitialLoadsComplete());
206 void AutomationProvider::DisableInitialLoadObservers() {
207 use_initial_load_observers_ = false;
208 OnInitialTabLoadsComplete();
209 OnOOBEWebuiReady();
212 int AutomationProvider::GetIndexForNavigationController(
213 const NavigationController* controller, const Browser* parent) const {
214 DCHECK(parent);
215 return parent->tab_strip_model()->GetIndexOfWebContents(
216 controller->GetWebContents());
219 // TODO(phajdan.jr): move to TestingAutomationProvider.
220 base::DictionaryValue* AutomationProvider::GetDictionaryFromDownloadItem(
221 const DownloadItem* download, bool incognito) {
222 const char *download_state_string = NULL;
223 switch (download->GetState()) {
224 case DownloadItem::IN_PROGRESS:
225 download_state_string = "IN_PROGRESS";
226 break;
227 case DownloadItem::CANCELLED:
228 download_state_string = "CANCELLED";
229 break;
230 case DownloadItem::INTERRUPTED:
231 download_state_string = "INTERRUPTED";
232 break;
233 case DownloadItem::COMPLETE:
234 download_state_string = "COMPLETE";
235 break;
236 case DownloadItem::MAX_DOWNLOAD_STATE:
237 NOTREACHED();
238 download_state_string = "UNKNOWN";
239 break;
241 DCHECK(download_state_string);
242 if (!download_state_string)
243 download_state_string = "UNKNOWN";
245 const char* download_danger_type_string = NULL;
246 switch (download->GetDangerType()) {
247 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
248 download_danger_type_string = "NOT_DANGEROUS";
249 break;
250 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
251 download_danger_type_string = "DANGEROUS_FILE";
252 break;
253 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
254 download_danger_type_string = "DANGEROUS_URL";
255 break;
256 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
257 download_danger_type_string = "DANGEROUS_CONTENT";
258 break;
259 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
260 download_danger_type_string = "MAYBE_DANGEROUS_CONTENT";
261 break;
262 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
263 download_danger_type_string = "UNCOMMON_CONTENT";
264 break;
265 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
266 download_danger_type_string = "USER_VALIDATED";
267 break;
268 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
269 download_danger_type_string = "DANGEROUS_HOST";
270 break;
271 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
272 download_danger_type_string = "POTENTIALLY_UNWANTED";
273 break;
274 case content::DOWNLOAD_DANGER_TYPE_MAX:
275 NOTREACHED();
276 download_danger_type_string = "UNKNOWN";
277 break;
279 DCHECK(download_danger_type_string);
280 if (!download_danger_type_string)
281 download_danger_type_string = "UNKNOWN";
283 base::DictionaryValue* dl_item_value = new base::DictionaryValue;
284 dl_item_value->SetInteger("id", static_cast<int>(download->GetId()));
285 dl_item_value->SetString("url", download->GetURL().spec());
286 dl_item_value->SetString("referrer_url", download->GetReferrerUrl().spec());
287 dl_item_value->SetString("file_name",
288 download->GetFileNameToReportUser().value());
289 dl_item_value->SetString("full_path",
290 download->GetTargetFilePath().value());
291 dl_item_value->SetBoolean("is_paused", download->IsPaused());
292 dl_item_value->SetBoolean("open_when_complete",
293 download->GetOpenWhenComplete());
294 dl_item_value->SetBoolean("is_temporary", download->IsTemporary());
295 dl_item_value->SetBoolean("is_otr", incognito);
296 dl_item_value->SetString("state", download_state_string);
297 dl_item_value->SetString("danger_type", download_danger_type_string);
298 dl_item_value->SetInteger("PercentComplete", download->PercentComplete());
300 return dl_item_value;
303 void AutomationProvider::OnChannelConnected(int pid) {
304 is_connected_ = true;
306 // Send a hello message with our current automation protocol version.
307 VLOG(2) << "Testing channel connected, sending hello message";
308 channel_->Send(new AutomationMsg_Hello(GetProtocolVersion()));
310 SendInitialLoadMessage();
313 bool AutomationProvider::OnMessageReceived(const IPC::Message& message) {
314 bool handled = true;
315 bool deserialize_success = true;
316 IPC_BEGIN_MESSAGE_MAP_EX(AutomationProvider, message, deserialize_success)
317 IPC_MESSAGE_HANDLER(AutomationMsg_HandleUnused, HandleUnused)
318 IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_Find, HandleFindRequest)
319 IPC_MESSAGE_HANDLER(AutomationMsg_OverrideEncoding, OverrideEncoding)
320 IPC_MESSAGE_HANDLER(AutomationMsg_SelectAll, SelectAll)
321 IPC_MESSAGE_HANDLER(AutomationMsg_Cut, Cut)
322 IPC_MESSAGE_HANDLER(AutomationMsg_Copy, Copy)
323 IPC_MESSAGE_HANDLER(AutomationMsg_Paste, Paste)
324 IPC_MESSAGE_HANDLER(AutomationMsg_ReloadAsync, ReloadAsync)
325 IPC_MESSAGE_HANDLER(AutomationMsg_StopAsync, StopAsync)
326 IPC_MESSAGE_HANDLER(AutomationMsg_SetPageFontSize, OnSetPageFontSize)
327 IPC_MESSAGE_HANDLER(AutomationMsg_JavaScriptStressTestControl,
328 JavaScriptStressTestControl)
329 IPC_MESSAGE_HANDLER(AutomationMsg_BeginTracing, BeginTracing)
330 IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_EndTracing, EndTracing)
331 IPC_MESSAGE_UNHANDLED(handled = false)
332 IPC_END_MESSAGE_MAP_EX()
333 if (!handled)
334 OnUnhandledMessage(message);
335 if (!deserialize_success)
336 OnMessageDeserializationFailure();
337 return handled;
340 void AutomationProvider::OnUnhandledMessage(const IPC::Message& message) {
341 // We should not hang here. Print a message to indicate what's going on,
342 // and disconnect the channel to notify the caller about the error
343 // in a way it can't ignore, and make any further attempts to send
344 // messages fail fast.
345 LOG(ERROR) << "AutomationProvider received a message it can't handle. "
346 << "Message type: " << message.type()
347 << ", routing ID: " << message.routing_id() << ". "
348 << "Please make sure that you use switches::kTestingChannelID "
349 << "for test code (TestingAutomationProvider), and "
350 << "switches::kAutomationClientChannelID for everything else "
351 << "(like ChromeFrame). Closing the automation channel.";
352 channel_->Close();
355 void AutomationProvider::OnMessageDeserializationFailure() {
356 LOG(ERROR) << "Failed to deserialize IPC message. "
357 << "Closing the automation channel.";
358 channel_->Close();
361 void AutomationProvider::HandleUnused(const IPC::Message& message, int handle) {
362 if (window_tracker_->ContainsHandle(handle)) {
363 window_tracker_->Remove(window_tracker_->GetResource(handle));
367 bool AutomationProvider::ReinitializeChannel() {
368 base::ThreadRestrictions::ScopedAllowIO allow_io;
370 // Make sure any old channels are cleaned up before starting up a new one.
371 channel_.reset();
372 return InitializeChannel(channel_id_);
375 void AutomationProvider::OnChannelError() {
376 if (reinitialize_on_channel_error_) {
377 VLOG(1) << "AutomationProxy disconnected, resetting AutomationProvider.";
378 if (ReinitializeChannel())
379 return;
380 VLOG(1) << "Error reinitializing AutomationProvider channel.";
382 VLOG(1) << "AutomationProxy went away, shutting down app.";
383 g_browser_process->GetAutomationProviderList()->RemoveProvider(this);
386 bool AutomationProvider::Send(IPC::Message* msg) {
387 DCHECK(channel_.get());
388 return channel_->Send(msg);
391 Browser* AutomationProvider::FindAndActivateTab(
392 NavigationController* controller) {
393 content::WebContentsDelegate* d = controller->GetWebContents()->GetDelegate();
394 if (d)
395 d->ActivateContents(controller->GetWebContents());
396 return chrome::FindBrowserWithWebContents(controller->GetWebContents());
399 void AutomationProvider::HandleFindRequest(
400 int handle,
401 const AutomationMsg_Find_Params& params,
402 IPC::Message* reply_message) {
403 if (!tab_tracker_->ContainsHandle(handle)) {
404 AutomationMsg_Find::WriteReplyParams(reply_message, -1, -1);
405 Send(reply_message);
406 return;
409 NavigationController* nav = tab_tracker_->GetResource(handle);
410 WebContents* web_contents = nav->GetWebContents();
412 SendFindRequest(web_contents,
413 false,
414 params.search_string,
415 params.forward,
416 params.match_case,
417 params.find_next,
418 reply_message);
421 void AutomationProvider::SendFindRequest(
422 WebContents* web_contents,
423 bool with_json,
424 const base::string16& search_string,
425 bool forward,
426 bool match_case,
427 bool find_next,
428 IPC::Message* reply_message) {
429 int request_id = FindInPageNotificationObserver::kFindInPageRequestId;
430 FindInPageNotificationObserver* observer =
431 new FindInPageNotificationObserver(this,
432 web_contents,
433 with_json,
434 reply_message);
435 if (!with_json) {
436 find_in_page_observer_.reset(observer);
438 FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);
439 if (find_tab_helper)
440 find_tab_helper->set_current_find_request_id(request_id);
442 WebFindOptions options;
443 options.forward = forward;
444 options.matchCase = match_case;
445 options.findNext = find_next;
446 web_contents->Find(
447 FindInPageNotificationObserver::kFindInPageRequestId, search_string,
448 options);
451 WebContents* AutomationProvider::GetWebContentsForHandle(
452 int handle, NavigationController** tab) {
453 if (tab_tracker_->ContainsHandle(handle)) {
454 NavigationController* nav_controller = tab_tracker_->GetResource(handle);
455 if (tab)
456 *tab = nav_controller;
457 return nav_controller->GetWebContents();
459 return NULL;
462 // Gets the current used encoding name of the page in the specified tab.
463 void AutomationProvider::OverrideEncoding(int tab_handle,
464 const std::string& encoding_name,
465 bool* success) {
466 *success = false;
467 if (tab_tracker_->ContainsHandle(tab_handle)) {
468 NavigationController* nav = tab_tracker_->GetResource(tab_handle);
469 if (!nav)
470 return;
471 Browser* browser = FindAndActivateTab(nav);
473 // If the browser has UI, simulate what a user would do.
474 // Activate the tab and then click the encoding menu.
475 if (browser && chrome::IsCommandEnabled(browser, IDC_ENCODING_MENU)) {
476 int selected_encoding_id =
477 CharacterEncoding::GetCommandIdByCanonicalEncodingName(encoding_name);
478 if (selected_encoding_id) {
479 browser->OverrideEncoding(selected_encoding_id);
480 *success = true;
482 } else {
483 // There is no UI, Chrome probably runs as Chrome-Frame mode.
484 // Try to get WebContents and call its SetOverrideEncoding method.
485 WebContents* contents = nav->GetWebContents();
486 if (!contents)
487 return;
488 const std::string selected_encoding =
489 CharacterEncoding::GetCanonicalEncodingNameByAliasName(encoding_name);
490 if (selected_encoding.empty())
491 return;
492 contents->SetOverrideEncoding(selected_encoding);
497 void AutomationProvider::SelectAll(int tab_handle) {
498 RenderViewHost* view = GetViewForTab(tab_handle);
499 if (!view) {
500 NOTREACHED();
501 return;
504 view->SelectAll();
507 void AutomationProvider::Cut(int tab_handle) {
508 RenderViewHost* view = GetViewForTab(tab_handle);
509 if (!view) {
510 NOTREACHED();
511 return;
514 view->Cut();
517 void AutomationProvider::Copy(int tab_handle) {
518 RenderViewHost* view = GetViewForTab(tab_handle);
519 if (!view) {
520 NOTREACHED();
521 return;
524 view->Copy();
527 void AutomationProvider::Paste(int tab_handle) {
528 RenderViewHost* view = GetViewForTab(tab_handle);
529 if (!view) {
530 NOTREACHED();
531 return;
534 view->Paste();
537 void AutomationProvider::ReloadAsync(int tab_handle) {
538 if (tab_tracker_->ContainsHandle(tab_handle)) {
539 NavigationController* tab = tab_tracker_->GetResource(tab_handle);
540 if (!tab) {
541 NOTREACHED();
542 return;
545 const bool check_for_repost = true;
546 tab->Reload(check_for_repost);
550 void AutomationProvider::StopAsync(int tab_handle) {
551 RenderViewHost* view = GetViewForTab(tab_handle);
552 if (!view) {
553 // We tolerate StopAsync being called even before a view has been created.
554 // So just log a warning instead of a NOTREACHED().
555 DLOG(WARNING) << "StopAsync: no view for handle " << tab_handle;
556 return;
559 view->Stop();
562 void AutomationProvider::OnSetPageFontSize(int tab_handle,
563 int font_size) {
564 AutomationPageFontSize automation_font_size =
565 static_cast<AutomationPageFontSize>(font_size);
567 if (automation_font_size < SMALLEST_FONT ||
568 automation_font_size > LARGEST_FONT) {
569 DLOG(ERROR) << "Invalid font size specified : "
570 << font_size;
571 return;
574 if (tab_tracker_->ContainsHandle(tab_handle)) {
575 NavigationController* tab = tab_tracker_->GetResource(tab_handle);
576 DCHECK(tab != NULL);
577 if (tab && tab->GetWebContents()) {
578 DCHECK(tab->GetWebContents()->GetBrowserContext() != NULL);
579 Profile* profile = Profile::FromBrowserContext(
580 tab->GetWebContents()->GetBrowserContext());
581 profile->GetPrefs()->SetInteger(prefs::kWebKitDefaultFontSize, font_size);
586 void AutomationProvider::JavaScriptStressTestControl(int tab_handle,
587 int cmd,
588 int param) {
589 RenderViewHost* view = GetViewForTab(tab_handle);
590 if (!view) {
591 NOTREACHED();
592 return;
595 view->Send(new ChromeViewMsg_JavaScriptStressTestControl(
596 view->GetRoutingID(), cmd, param));
599 void AutomationProvider::BeginTracing(const std::string& category_patterns,
600 bool* success) {
601 *success = TracingController::GetInstance()->EnableRecording(
602 category_patterns, TracingController::DEFAULT_OPTIONS,
603 TracingController::EnableRecordingDoneCallback());
606 void AutomationProvider::EndTracing(IPC::Message* reply_message) {
607 base::FilePath path;
608 if (!TracingController::GetInstance()->DisableRecording(
609 path, base::Bind(&AutomationProvider::OnTraceDataCollected, this,
610 reply_message))) {
611 // If failed to call EndTracingAsync, need to reply with failure now.
612 AutomationMsg_EndTracing::WriteReplyParams(reply_message, path, false);
613 Send(reply_message);
615 // Otherwise defer EndTracing reply until TraceController calls us back.
618 void AutomationProvider::OnTraceDataCollected(IPC::Message* reply_message,
619 const base::FilePath& path) {
620 if (reply_message) {
621 AutomationMsg_EndTracing::WriteReplyParams(reply_message, path, true);
622 Send(reply_message);
626 RenderViewHost* AutomationProvider::GetViewForTab(int tab_handle) {
627 if (tab_tracker_->ContainsHandle(tab_handle)) {
628 NavigationController* tab = tab_tracker_->GetResource(tab_handle);
629 if (!tab) {
630 NOTREACHED();
631 return NULL;
634 WebContents* web_contents = tab->GetWebContents();
635 if (!web_contents) {
636 NOTREACHED();
637 return NULL;
640 RenderViewHost* view_host = web_contents->GetRenderViewHost();
641 return view_host;
644 return NULL;