tab_helper.cc revision f2477e01787aa58f445919b809d89e252beef54f
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/browser/extensions/tab_helper.h" 6 7#include "base/logging.h" 8#include "base/strings/string_util.h" 9#include "chrome/browser/chrome_notification_types.h" 10#include "chrome/browser/extensions/activity_log/activity_log.h" 11#include "chrome/browser/extensions/api/declarative/rules_registry_service.h" 12#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h" 13#include "chrome/browser/extensions/crx_installer.h" 14#include "chrome/browser/extensions/error_console/error_console.h" 15#include "chrome/browser/extensions/extension_action.h" 16#include "chrome/browser/extensions/extension_action_manager.h" 17#include "chrome/browser/extensions/extension_service.h" 18#include "chrome/browser/extensions/extension_system.h" 19#include "chrome/browser/extensions/extension_tab_util.h" 20#include "chrome/browser/extensions/image_loader.h" 21#include "chrome/browser/extensions/page_action_controller.h" 22#include "chrome/browser/extensions/script_badge_controller.h" 23#include "chrome/browser/extensions/script_bubble_controller.h" 24#include "chrome/browser/extensions/script_executor.h" 25#include "chrome/browser/extensions/webstore_inline_installer.h" 26#include "chrome/browser/extensions/webstore_inline_installer_factory.h" 27#include "chrome/browser/profiles/profile.h" 28#include "chrome/browser/sessions/session_id.h" 29#include "chrome/browser/sessions/session_tab_helper.h" 30#include "chrome/browser/shell_integration.h" 31#include "chrome/browser/ui/app_list/app_list_service.h" 32#include "chrome/browser/ui/app_list/app_list_util.h" 33#include "chrome/browser/ui/browser_commands.h" 34#include "chrome/browser/ui/browser_dialogs.h" 35#include "chrome/browser/ui/browser_finder.h" 36#include "chrome/browser/ui/host_desktop.h" 37#include "chrome/browser/ui/web_applications/web_app_ui.h" 38#include "chrome/browser/web_applications/web_app.h" 39#include "chrome/common/extensions/extension_constants.h" 40#include "chrome/common/extensions/extension_icon_set.h" 41#include "chrome/common/extensions/extension_messages.h" 42#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 43#include "chrome/common/extensions/manifest_handlers/icons_handler.h" 44#include "chrome/common/render_messages.h" 45#include "chrome/common/url_constants.h" 46#include "content/public/browser/invalidate_type.h" 47#include "content/public/browser/navigation_controller.h" 48#include "content/public/browser/navigation_details.h" 49#include "content/public/browser/navigation_entry.h" 50#include "content/public/browser/notification_service.h" 51#include "content/public/browser/notification_source.h" 52#include "content/public/browser/notification_types.h" 53#include "content/public/browser/render_process_host.h" 54#include "content/public/browser/render_view_host.h" 55#include "content/public/browser/render_widget_host_view.h" 56#include "content/public/browser/web_contents.h" 57#include "content/public/browser/web_contents_view.h" 58#include "extensions/browser/extension_error.h" 59#include "extensions/common/extension.h" 60#include "extensions/common/extension_resource.h" 61#include "extensions/common/extension_urls.h" 62#include "extensions/common/feature_switch.h" 63#include "ui/gfx/image/image.h" 64 65using content::NavigationController; 66using content::NavigationEntry; 67using content::RenderViewHost; 68using content::WebContents; 69 70DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper); 71 72namespace extensions { 73 74TabHelper::ScriptExecutionObserver::ScriptExecutionObserver( 75 TabHelper* tab_helper) 76 : tab_helper_(tab_helper) { 77 tab_helper_->AddScriptExecutionObserver(this); 78} 79 80TabHelper::ScriptExecutionObserver::ScriptExecutionObserver() 81 : tab_helper_(NULL) { 82} 83 84TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() { 85 if (tab_helper_) 86 tab_helper_->RemoveScriptExecutionObserver(this); 87} 88 89TabHelper::TabHelper(content::WebContents* web_contents) 90 : content::WebContentsObserver(web_contents), 91 extension_app_(NULL), 92 extension_function_dispatcher_( 93 Profile::FromBrowserContext(web_contents->GetBrowserContext()), this), 94 pending_web_app_action_(NONE), 95 script_executor_(new ScriptExecutor(web_contents, 96 &script_execution_observers_)), 97 image_loader_ptr_factory_(this), 98 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) { 99 // The ActiveTabPermissionManager requires a session ID; ensure this 100 // WebContents has one. 101 SessionTabHelper::CreateForWebContents(web_contents); 102 if (web_contents->GetRenderViewHost()) 103 SetTabId(web_contents->GetRenderViewHost()); 104 active_tab_permission_granter_.reset(new ActiveTabPermissionGranter( 105 web_contents, 106 SessionID::IdForTab(web_contents), 107 Profile::FromBrowserContext(web_contents->GetBrowserContext()))); 108 if (FeatureSwitch::script_badges()->IsEnabled()) { 109 location_bar_controller_.reset( 110 new ScriptBadgeController(web_contents, this)); 111 } else { 112 location_bar_controller_.reset( 113 new PageActionController(web_contents)); 114 } 115 116 if (FeatureSwitch::script_bubble()->IsEnabled()) { 117 script_bubble_controller_.reset( 118 new ScriptBubbleController(web_contents, this)); 119 } 120 121 // If more classes need to listen to global content script activity, then 122 // a separate routing class with an observer interface should be written. 123 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); 124 125#if defined(ENABLE_EXTENSIONS) 126 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_)); 127#endif 128 129 registrar_.Add(this, 130 content::NOTIFICATION_LOAD_STOP, 131 content::Source<NavigationController>( 132 &web_contents->GetController())); 133 134 registrar_.Add(this, 135 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 136 content::Source<CrxInstaller>(NULL)); 137 138 registrar_.Add(this, 139 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 140 content::NotificationService::AllSources()); 141 142 registrar_.Add(this, 143 chrome::NOTIFICATION_EXTENSION_UNLOADED, 144 content::NotificationService::AllSources()); 145} 146 147TabHelper::~TabHelper() { 148#if defined(ENABLE_EXTENSIONS) 149 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_)); 150#endif 151} 152 153void TabHelper::CreateApplicationShortcuts() { 154 DCHECK(CanCreateApplicationShortcuts()); 155 NavigationEntry* entry = 156 web_contents()->GetController().GetLastCommittedEntry(); 157 if (!entry) 158 return; 159 160 pending_web_app_action_ = CREATE_SHORTCUT; 161 162 // Start fetching web app info for CreateApplicationShortcut dialog and show 163 // the dialog when the data is available in OnDidGetApplicationInfo. 164 GetApplicationInfo(entry->GetPageID()); 165} 166 167void TabHelper::CreateHostedAppFromWebContents() { 168 DCHECK(CanCreateApplicationShortcuts()); 169 NavigationEntry* entry = 170 web_contents()->GetController().GetLastCommittedEntry(); 171 if (!entry) 172 return; 173 174 pending_web_app_action_ = CREATE_HOSTED_APP; 175 176 // Start fetching web app info for CreateApplicationShortcut dialog and show 177 // the dialog when the data is available in OnDidGetApplicationInfo. 178 GetApplicationInfo(entry->GetPageID()); 179} 180 181bool TabHelper::CanCreateApplicationShortcuts() const { 182#if defined(OS_MACOSX) 183 return false; 184#else 185 return web_app::IsValidUrl(web_contents()->GetURL()) && 186 pending_web_app_action_ == NONE; 187#endif 188} 189 190void TabHelper::SetExtensionApp(const Extension* extension) { 191 DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid()); 192 extension_app_ = extension; 193 194 UpdateExtensionAppIcon(extension_app_); 195 196 content::NotificationService::current()->Notify( 197 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, 198 content::Source<TabHelper>(this), 199 content::NotificationService::NoDetails()); 200} 201 202void TabHelper::SetExtensionAppById(const std::string& extension_app_id) { 203 const Extension* extension = GetExtension(extension_app_id); 204 if (extension) 205 SetExtensionApp(extension); 206} 207 208void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) { 209 const Extension* extension = GetExtension(extension_app_id); 210 if (extension) 211 UpdateExtensionAppIcon(extension); 212} 213 214SkBitmap* TabHelper::GetExtensionAppIcon() { 215 if (extension_app_icon_.empty()) 216 return NULL; 217 218 return &extension_app_icon_; 219} 220 221void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) { 222 SetTabId(render_view_host); 223} 224 225void TabHelper::DidNavigateMainFrame( 226 const content::LoadCommittedDetails& details, 227 const content::FrameNavigateParams& params) { 228#if defined(ENABLE_EXTENSIONS) 229 if (ExtensionSystem::Get(profile_)->extension_service() && 230 RulesRegistryService::Get(profile_)) { 231 RulesRegistryService::Get(profile_)->content_rules_registry()-> 232 DidNavigateMainFrame(web_contents(), details, params); 233 } 234#endif // defined(ENABLE_EXTENSIONS) 235 236 if (details.is_in_page) 237 return; 238 239 Profile* profile = 240 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 241 ExtensionService* service = profile->GetExtensionService(); 242 if (!service) 243 return; 244 245 ExtensionActionManager* extension_action_manager = 246 ExtensionActionManager::Get(profile); 247 for (ExtensionSet::const_iterator it = service->extensions()->begin(); 248 it != service->extensions()->end(); ++it) { 249 ExtensionAction* browser_action = 250 extension_action_manager->GetBrowserAction(*it->get()); 251 if (browser_action) { 252 browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents())); 253 content::NotificationService::current()->Notify( 254 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED, 255 content::Source<ExtensionAction>(browser_action), 256 content::NotificationService::NoDetails()); 257 } 258 } 259} 260 261bool TabHelper::OnMessageReceived(const IPC::Message& message) { 262 bool handled = true; 263 IPC_BEGIN_MESSAGE_MAP(TabHelper, message) 264 IPC_MESSAGE_HANDLER(ExtensionHostMsg_DidGetApplicationInfo, 265 OnDidGetApplicationInfo) 266 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall, 267 OnInlineWebstoreInstall) 268 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState, 269 OnGetAppInstallState); 270 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest) 271 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting, 272 OnContentScriptsExecuting) 273 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange, 274 OnWatchedPageChange) 275 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded, 276 OnDetailedConsoleMessageAdded) 277 IPC_MESSAGE_UNHANDLED(handled = false) 278 IPC_END_MESSAGE_MAP() 279 return handled; 280} 281 282void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents, 283 WebContents* new_web_contents) { 284 // When the WebContents that this is attached to is cloned, give the new clone 285 // a TabHelper and copy state over. 286 CreateForWebContents(new_web_contents); 287 TabHelper* new_helper = FromWebContents(new_web_contents); 288 289 new_helper->SetExtensionApp(extension_app()); 290 new_helper->extension_app_icon_ = extension_app_icon_; 291} 292 293void TabHelper::OnDidGetApplicationInfo(int32 page_id, 294 const WebApplicationInfo& info) { 295 // Android does not implement BrowserWindow. 296#if !defined(OS_MACOSX) && !defined(OS_ANDROID) 297 web_app_info_ = info; 298 299 NavigationEntry* entry = 300 web_contents()->GetController().GetLastCommittedEntry(); 301 if (!entry || (entry->GetPageID() != page_id)) 302 return; 303 304 switch (pending_web_app_action_) { 305 case CREATE_SHORTCUT: { 306 chrome::ShowCreateWebAppShortcutsDialog( 307 web_contents()->GetView()->GetTopLevelNativeWindow(), 308 web_contents()); 309 break; 310 } 311 case CREATE_HOSTED_APP: { 312 CreateHostedApp(info); 313 break; 314 } 315 case UPDATE_SHORTCUT: { 316 web_app::UpdateShortcutForTabContents(web_contents()); 317 break; 318 } 319 default: 320 NOTREACHED(); 321 break; 322 } 323 324 // The hosted app action will be cleared once the installation completes or 325 // fails. 326 if (pending_web_app_action_ != CREATE_HOSTED_APP) 327 pending_web_app_action_ = NONE; 328#endif 329} 330 331void TabHelper::CreateHostedApp(const WebApplicationInfo& info) { 332 ShellIntegration::ShortcutInfo shortcut_info; 333 web_app::GetShortcutInfoForTab(web_contents(), &shortcut_info); 334 WebApplicationInfo web_app_info; 335 336 web_app_info.is_bookmark_app = true; 337 web_app_info.app_url = shortcut_info.url; 338 web_app_info.title = shortcut_info.title; 339 web_app_info.urls.push_back(web_app_info.app_url); 340 341 // TODO(calamity): this should attempt to download the best icon that it can 342 // from |info.icons| rather than just using the favicon as it scales up badly. 343 // Fix this once |info.icons| gets populated commonly. 344 345 // Get the smallest icon in the icon family (should have only 1). 346 const gfx::Image* icon = shortcut_info.favicon.GetBest(0, 0); 347 SkBitmap bitmap = icon ? icon->AsBitmap() : SkBitmap(); 348 349 if (!icon->IsEmpty()) { 350 WebApplicationInfo::IconInfo icon_info; 351 icon_info.data = bitmap; 352 icon_info.width = icon_info.data.width(); 353 icon_info.height = icon_info.data.height(); 354 web_app_info.icons.push_back(icon_info); 355 } 356 357 ExtensionService* service = profile_->GetExtensionService(); 358 scoped_refptr<extensions::CrxInstaller> installer( 359 extensions::CrxInstaller::CreateSilent(service)); 360 installer->set_error_on_unsupported_requirements(true); 361 installer->InstallWebApp(web_app_info); 362} 363 364void TabHelper::OnInlineWebstoreInstall( 365 int install_id, 366 int return_route_id, 367 const std::string& webstore_item_id, 368 const GURL& requestor_url) { 369 WebstoreStandaloneInstaller::Callback callback = 370 base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this), 371 install_id, return_route_id); 372 scoped_refptr<WebstoreInlineInstaller> installer( 373 webstore_inline_installer_factory_->CreateInstaller( 374 web_contents(), 375 webstore_item_id, 376 requestor_url, 377 callback)); 378 installer->BeginInstall(); 379} 380 381void TabHelper::OnGetAppInstallState(const GURL& requestor_url, 382 int return_route_id, 383 int callback_id) { 384 Profile* profile = 385 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 386 ExtensionService* extension_service = profile->GetExtensionService(); 387 const ExtensionSet* extensions = extension_service->extensions(); 388 const ExtensionSet* disabled = extension_service->disabled_extensions(); 389 390 std::string state; 391 if (extensions->GetHostedAppByURL(requestor_url)) 392 state = extension_misc::kAppStateInstalled; 393 else if (disabled->GetHostedAppByURL(requestor_url)) 394 state = extension_misc::kAppStateDisabled; 395 else 396 state = extension_misc::kAppStateNotInstalled; 397 398 Send(new ExtensionMsg_GetAppInstallStateResponse( 399 return_route_id, state, callback_id)); 400} 401 402void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) { 403 extension_function_dispatcher_.Dispatch(request, 404 web_contents()->GetRenderViewHost()); 405} 406 407void TabHelper::OnContentScriptsExecuting( 408 const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map, 409 int32 on_page_id, 410 const GURL& on_url) { 411 FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_, 412 OnScriptsExecuted(web_contents(), 413 executing_scripts_map, 414 on_page_id, 415 on_url)); 416} 417 418void TabHelper::OnWatchedPageChange( 419 const std::vector<std::string>& css_selectors) { 420#if defined(ENABLE_EXTENSIONS) 421 if (ExtensionSystem::Get(profile_)->extension_service() && 422 RulesRegistryService::Get(profile_)) { 423 RulesRegistryService::Get(profile_)->content_rules_registry()->Apply( 424 web_contents(), css_selectors); 425 } 426#endif // defined(ENABLE_EXTENSIONS) 427} 428 429void TabHelper::OnDetailedConsoleMessageAdded( 430 const base::string16& message, 431 const base::string16& source, 432 const StackTrace& stack_trace, 433 int32 severity_level) { 434 if (IsSourceFromAnExtension(source)) { 435 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); 436 ErrorConsole::Get(profile_)->ReportError( 437 scoped_ptr<ExtensionError>(new RuntimeError( 438 extension_app_ ? extension_app_->id() : std::string(), 439 profile_->IsOffTheRecord(), 440 source, 441 message, 442 stack_trace, 443 web_contents() ? 444 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(), 445 static_cast<logging::LogSeverity>(severity_level), 446 rvh->GetRoutingID(), 447 rvh->GetProcess()->GetID()))); 448 } 449} 450 451const Extension* TabHelper::GetExtension(const std::string& extension_app_id) { 452 if (extension_app_id.empty()) 453 return NULL; 454 455 Profile* profile = 456 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 457 ExtensionService* extension_service = profile->GetExtensionService(); 458 if (!extension_service || !extension_service->is_ready()) 459 return NULL; 460 461 const Extension* extension = 462 extension_service->GetExtensionById(extension_app_id, false); 463 return extension; 464} 465 466void TabHelper::UpdateExtensionAppIcon(const Extension* extension) { 467 extension_app_icon_.reset(); 468 // Ensure previously enqueued callbacks are ignored. 469 image_loader_ptr_factory_.InvalidateWeakPtrs(); 470 471 // Enqueue OnImageLoaded callback. 472 if (extension) { 473 Profile* profile = 474 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 475 extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile); 476 loader->LoadImageAsync( 477 extension, 478 IconsInfo::GetIconResource(extension, 479 extension_misc::EXTENSION_ICON_SMALLISH, 480 ExtensionIconSet::MATCH_EXACTLY), 481 gfx::Size(extension_misc::EXTENSION_ICON_SMALLISH, 482 extension_misc::EXTENSION_ICON_SMALLISH), 483 base::Bind(&TabHelper::OnImageLoaded, 484 image_loader_ptr_factory_.GetWeakPtr())); 485 } 486} 487 488void TabHelper::SetAppIcon(const SkBitmap& app_icon) { 489 extension_app_icon_ = app_icon; 490 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE); 491} 492 493void TabHelper::SetWebstoreInlineInstallerFactoryForTests( 494 WebstoreInlineInstallerFactory* factory) { 495 webstore_inline_installer_factory_.reset(factory); 496} 497 498void TabHelper::OnImageLoaded(const gfx::Image& image) { 499 if (!image.IsEmpty()) { 500 extension_app_icon_ = *image.ToSkBitmap(); 501 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); 502 } 503} 504 505WindowController* TabHelper::GetExtensionWindowController() const { 506 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents()); 507} 508 509void TabHelper::OnInlineInstallComplete(int install_id, 510 int return_route_id, 511 bool success, 512 const std::string& error) { 513 Send(new ExtensionMsg_InlineWebstoreInstallResponse( 514 return_route_id, install_id, success, success ? std::string() : error)); 515} 516 517WebContents* TabHelper::GetAssociatedWebContents() const { 518 return web_contents(); 519} 520 521void TabHelper::GetApplicationInfo(int32 page_id) { 522 Send(new ExtensionMsg_GetApplicationInfo(routing_id(), page_id)); 523} 524 525void TabHelper::Observe(int type, 526 const content::NotificationSource& source, 527 const content::NotificationDetails& details) { 528 switch (type) { 529 case content::NOTIFICATION_LOAD_STOP: { 530 const NavigationController& controller = 531 *content::Source<NavigationController>(source).ptr(); 532 DCHECK_EQ(controller.GetWebContents(), web_contents()); 533 534 if (pending_web_app_action_ == UPDATE_SHORTCUT) { 535 // Schedule a shortcut update when web application info is available if 536 // last committed entry is not NULL. Last committed entry could be NULL 537 // when an interstitial page is injected (e.g. bad https certificate, 538 // malware site etc). When this happens, we abort the shortcut update. 539 NavigationEntry* entry = controller.GetLastCommittedEntry(); 540 if (entry) 541 GetApplicationInfo(entry->GetPageID()); 542 else 543 pending_web_app_action_ = NONE; 544 } 545 break; 546 } 547 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { 548 if (pending_web_app_action_ != CREATE_HOSTED_APP) 549 return; 550 551 pending_web_app_action_ = NONE; 552 553 const Extension* extension = 554 content::Details<const Extension>(details).ptr(); 555 if (!extension || !extension->from_bookmark()) 556 return; 557 558 // If enabled, launch the app launcher and highlight the new app. 559 // Otherwise, open the chrome://apps page in a new foreground tab. 560 if (IsAppLauncherEnabled()) { 561 AppListService::Get(chrome::GetHostDesktopTypeForNativeView( 562 web_contents()->GetView()->GetNativeView()))-> 563 ShowForProfile(profile_); 564 565 content::NotificationService::current()->Notify( 566 chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST, 567 content::Source<Profile>(profile_), 568 content::Details<const std::string>(&extension->id())); 569 return; 570 } 571 572 // Android does not implement browser_finder.cc. 573#if !defined(OS_ANDROID) 574 Browser* browser = 575 chrome::FindBrowserWithWebContents(web_contents()); 576 if (browser) { 577 browser->OpenURL( 578 content::OpenURLParams(GURL(chrome::kChromeUIAppsURL), 579 content::Referrer(), 580 NEW_FOREGROUND_TAB, 581 content::PAGE_TRANSITION_LINK, 582 false)); 583 } 584#endif 585 } 586 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: { 587 if (pending_web_app_action_ == CREATE_HOSTED_APP) 588 pending_web_app_action_ = NONE; 589 break; 590 } 591 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 592 if (script_bubble_controller_) { 593 script_bubble_controller_->OnExtensionUnloaded( 594 content::Details<extensions::UnloadedExtensionInfo>( 595 details)->extension->id()); 596 break; 597 } 598 } 599 } 600} 601 602void TabHelper::SetTabId(RenderViewHost* render_view_host) { 603 render_view_host->Send( 604 new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(), 605 SessionID::IdForTab(web_contents()))); 606} 607 608} // namespace extensions 609