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