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