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