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