tab_helper.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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/ui/browser_dialogs.h" 31#include "chrome/browser/ui/web_applications/web_app_ui.h" 32#include "chrome/browser/web_applications/web_app.h" 33#include "chrome/common/extensions/extension.h" 34#include "chrome/common/extensions/extension_constants.h" 35#include "chrome/common/extensions/extension_icon_set.h" 36#include "chrome/common/extensions/extension_messages.h" 37#include "chrome/common/extensions/feature_switch.h" 38#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 39#include "chrome/common/extensions/manifest_handlers/icons_handler.h" 40#include "chrome/common/render_messages.h" 41#include "content/public/browser/invalidate_type.h" 42#include "content/public/browser/navigation_controller.h" 43#include "content/public/browser/navigation_details.h" 44#include "content/public/browser/navigation_entry.h" 45#include "content/public/browser/notification_service.h" 46#include "content/public/browser/notification_source.h" 47#include "content/public/browser/notification_types.h" 48#include "content/public/browser/render_process_host.h" 49#include "content/public/browser/render_view_host.h" 50#include "content/public/browser/render_widget_host_view.h" 51#include "content/public/browser/web_contents.h" 52#include "content/public/browser/web_contents_view.h" 53#include "extensions/browser/extension_error.h" 54#include "extensions/common/extension_resource.h" 55#include "extensions/common/extension_urls.h" 56#include "ui/gfx/image/image.h" 57 58using content::NavigationController; 59using content::NavigationEntry; 60using content::RenderViewHost; 61using content::WebContents; 62 63DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper); 64 65namespace extensions { 66 67TabHelper::ScriptExecutionObserver::ScriptExecutionObserver( 68 TabHelper* tab_helper) 69 : tab_helper_(tab_helper) { 70 tab_helper_->AddScriptExecutionObserver(this); 71} 72 73TabHelper::ScriptExecutionObserver::ScriptExecutionObserver() 74 : tab_helper_(NULL) { 75} 76 77TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() { 78 if (tab_helper_) 79 tab_helper_->RemoveScriptExecutionObserver(this); 80} 81 82TabHelper::TabHelper(content::WebContents* web_contents) 83 : content::WebContentsObserver(web_contents), 84 extension_app_(NULL), 85 extension_function_dispatcher_( 86 Profile::FromBrowserContext(web_contents->GetBrowserContext()), this), 87 pending_web_app_action_(NONE), 88 script_executor_(new ScriptExecutor(web_contents, 89 &script_execution_observers_)), 90 image_loader_ptr_factory_(this), 91 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) { 92 // The ActiveTabPermissionManager requires a session ID; ensure this 93 // WebContents has one. 94 SessionTabHelper::CreateForWebContents(web_contents); 95 if (web_contents->GetRenderViewHost()) 96 SetTabId(web_contents->GetRenderViewHost()); 97 active_tab_permission_granter_.reset(new ActiveTabPermissionGranter( 98 web_contents, 99 SessionID::IdForTab(web_contents), 100 Profile::FromBrowserContext(web_contents->GetBrowserContext()))); 101 if (FeatureSwitch::script_badges()->IsEnabled()) { 102 location_bar_controller_.reset( 103 new ScriptBadgeController(web_contents, this)); 104 } else { 105 location_bar_controller_.reset( 106 new PageActionController(web_contents)); 107 } 108 109 if (FeatureSwitch::script_bubble()->IsEnabled()) { 110 script_bubble_controller_.reset( 111 new ScriptBubbleController(web_contents, this)); 112 } 113 114 // If more classes need to listen to global content script activity, then 115 // a separate routing class with an observer interface should be written. 116 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); 117 118#if defined(ENABLE_EXTENSIONS) 119 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_)); 120#endif 121 122 registrar_.Add(this, 123 content::NOTIFICATION_LOAD_STOP, 124 content::Source<NavigationController>( 125 &web_contents->GetController())); 126 127 registrar_.Add(this, 128 chrome::NOTIFICATION_EXTENSION_UNLOADED, 129 content::NotificationService::AllSources()); 130} 131 132TabHelper::~TabHelper() { 133#if defined(ENABLE_EXTENSIONS) 134 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_)); 135#endif 136} 137 138void TabHelper::CreateApplicationShortcuts() { 139 DCHECK(CanCreateApplicationShortcuts()); 140 NavigationEntry* entry = 141 web_contents()->GetController().GetLastCommittedEntry(); 142 if (!entry) 143 return; 144 145 pending_web_app_action_ = CREATE_SHORTCUT; 146 147 // Start fetching web app info for CreateApplicationShortcut dialog and show 148 // the dialog when the data is available in OnDidGetApplicationInfo. 149 GetApplicationInfo(entry->GetPageID()); 150} 151 152bool TabHelper::CanCreateApplicationShortcuts() const { 153#if defined(OS_MACOSX) 154 return false; 155#else 156 return web_app::IsValidUrl(web_contents()->GetURL()) && 157 pending_web_app_action_ == NONE; 158#endif 159} 160 161void TabHelper::SetExtensionApp(const Extension* extension) { 162 DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid()); 163 extension_app_ = extension; 164 165 UpdateExtensionAppIcon(extension_app_); 166 167 content::NotificationService::current()->Notify( 168 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, 169 content::Source<TabHelper>(this), 170 content::NotificationService::NoDetails()); 171} 172 173void TabHelper::SetExtensionAppById(const std::string& extension_app_id) { 174 const Extension* extension = GetExtension(extension_app_id); 175 if (extension) 176 SetExtensionApp(extension); 177} 178 179void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) { 180 const Extension* extension = GetExtension(extension_app_id); 181 if (extension) 182 UpdateExtensionAppIcon(extension); 183} 184 185SkBitmap* TabHelper::GetExtensionAppIcon() { 186 if (extension_app_icon_.empty()) 187 return NULL; 188 189 return &extension_app_icon_; 190} 191 192void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) { 193 SetTabId(render_view_host); 194} 195 196void TabHelper::DidNavigateMainFrame( 197 const content::LoadCommittedDetails& details, 198 const content::FrameNavigateParams& params) { 199#if defined(ENABLE_EXTENSIONS) 200 if (ExtensionSystem::Get(profile_)->extension_service() && 201 RulesRegistryService::Get(profile_)) { 202 RulesRegistryService::Get(profile_)->content_rules_registry()-> 203 DidNavigateMainFrame(web_contents(), details, params); 204 } 205#endif // defined(ENABLE_EXTENSIONS) 206 207 if (details.is_in_page) 208 return; 209 210 Profile* profile = 211 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 212 ExtensionService* service = profile->GetExtensionService(); 213 if (!service) 214 return; 215 216 ExtensionActionManager* extension_action_manager = 217 ExtensionActionManager::Get(profile); 218 for (ExtensionSet::const_iterator it = service->extensions()->begin(); 219 it != service->extensions()->end(); ++it) { 220 ExtensionAction* browser_action = 221 extension_action_manager->GetBrowserAction(*it->get()); 222 if (browser_action) { 223 browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents())); 224 content::NotificationService::current()->Notify( 225 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED, 226 content::Source<ExtensionAction>(browser_action), 227 content::NotificationService::NoDetails()); 228 } 229 } 230} 231 232bool TabHelper::OnMessageReceived(const IPC::Message& message) { 233 bool handled = true; 234 IPC_BEGIN_MESSAGE_MAP(TabHelper, message) 235 IPC_MESSAGE_HANDLER(ExtensionHostMsg_DidGetApplicationInfo, 236 OnDidGetApplicationInfo) 237 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall, 238 OnInlineWebstoreInstall) 239 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState, 240 OnGetAppInstallState); 241 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest) 242 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting, 243 OnContentScriptsExecuting) 244 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange, 245 OnWatchedPageChange) 246 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded, 247 OnDetailedConsoleMessageAdded) 248 IPC_MESSAGE_UNHANDLED(handled = false) 249 IPC_END_MESSAGE_MAP() 250 return handled; 251} 252 253void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents, 254 WebContents* new_web_contents) { 255 // When the WebContents that this is attached to is cloned, give the new clone 256 // a TabHelper and copy state over. 257 CreateForWebContents(new_web_contents); 258 TabHelper* new_helper = FromWebContents(new_web_contents); 259 260 new_helper->SetExtensionApp(extension_app()); 261 new_helper->extension_app_icon_ = extension_app_icon_; 262} 263 264void TabHelper::OnDidGetApplicationInfo(int32 page_id, 265 const WebApplicationInfo& info) { 266 // Android does not implement BrowserWindow. 267#if !defined(OS_MACOSX) && !defined(OS_ANDROID) 268 web_app_info_ = info; 269 270 NavigationEntry* entry = 271 web_contents()->GetController().GetLastCommittedEntry(); 272 if (!entry || (entry->GetPageID() != page_id)) 273 return; 274 275 switch (pending_web_app_action_) { 276 case CREATE_SHORTCUT: { 277 chrome::ShowCreateWebAppShortcutsDialog( 278 web_contents()->GetView()->GetTopLevelNativeWindow(), 279 web_contents()); 280 break; 281 } 282 case UPDATE_SHORTCUT: { 283 web_app::UpdateShortcutForTabContents(web_contents()); 284 break; 285 } 286 default: 287 NOTREACHED(); 288 break; 289 } 290 291 pending_web_app_action_ = NONE; 292#endif 293} 294 295void TabHelper::OnInlineWebstoreInstall( 296 int install_id, 297 int return_route_id, 298 const std::string& webstore_item_id, 299 const GURL& requestor_url) { 300 WebstoreStandaloneInstaller::Callback callback = 301 base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this), 302 install_id, return_route_id); 303 scoped_refptr<WebstoreInlineInstaller> installer( 304 webstore_inline_installer_factory_->CreateInstaller( 305 web_contents(), 306 webstore_item_id, 307 requestor_url, 308 callback)); 309 installer->BeginInstall(); 310} 311 312void TabHelper::OnGetAppInstallState(const GURL& requestor_url, 313 int return_route_id, 314 int callback_id) { 315 Profile* profile = 316 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 317 ExtensionService* extension_service = profile->GetExtensionService(); 318 const ExtensionSet* extensions = extension_service->extensions(); 319 const ExtensionSet* disabled = extension_service->disabled_extensions(); 320 321 std::string state; 322 if (extensions->GetHostedAppByURL(requestor_url)) 323 state = extension_misc::kAppStateInstalled; 324 else if (disabled->GetHostedAppByURL(requestor_url)) 325 state = extension_misc::kAppStateDisabled; 326 else 327 state = extension_misc::kAppStateNotInstalled; 328 329 Send(new ExtensionMsg_GetAppInstallStateResponse( 330 return_route_id, state, callback_id)); 331} 332 333void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) { 334 extension_function_dispatcher_.Dispatch(request, 335 web_contents()->GetRenderViewHost()); 336} 337 338void TabHelper::OnContentScriptsExecuting( 339 const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map, 340 int32 on_page_id, 341 const GURL& on_url) { 342 FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_, 343 OnScriptsExecuted(web_contents(), 344 executing_scripts_map, 345 on_page_id, 346 on_url)); 347} 348 349void TabHelper::OnWatchedPageChange( 350 const std::vector<std::string>& css_selectors) { 351#if defined(ENABLE_EXTENSIONS) 352 if (ExtensionSystem::Get(profile_)->extension_service() && 353 RulesRegistryService::Get(profile_)) { 354 RulesRegistryService::Get(profile_)->content_rules_registry()->Apply( 355 web_contents(), css_selectors); 356 } 357#endif // defined(ENABLE_EXTENSIONS) 358} 359 360void TabHelper::OnDetailedConsoleMessageAdded( 361 const base::string16& message, 362 const base::string16& source, 363 const StackTrace& stack_trace, 364 int32 severity_level) { 365 if (IsSourceFromAnExtension(source)) { 366 ErrorConsole::Get(profile_)->ReportError( 367 scoped_ptr<ExtensionError>(new RuntimeError( 368 extension_app_ ? extension_app_->id() : EmptyString(), 369 profile_->IsOffTheRecord(), 370 source, 371 message, 372 stack_trace, 373 web_contents() ? 374 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(), 375 static_cast<logging::LogSeverity>(severity_level)))); 376 } 377} 378 379const Extension* TabHelper::GetExtension(const std::string& extension_app_id) { 380 if (extension_app_id.empty()) 381 return NULL; 382 383 Profile* profile = 384 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 385 ExtensionService* extension_service = profile->GetExtensionService(); 386 if (!extension_service || !extension_service->is_ready()) 387 return NULL; 388 389 const Extension* extension = 390 extension_service->GetExtensionById(extension_app_id, false); 391 return extension; 392} 393 394void TabHelper::UpdateExtensionAppIcon(const Extension* extension) { 395 extension_app_icon_.reset(); 396 // Ensure previously enqueued callbacks are ignored. 397 image_loader_ptr_factory_.InvalidateWeakPtrs(); 398 399 // Enqueue OnImageLoaded callback. 400 if (extension) { 401 Profile* profile = 402 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); 403 extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile); 404 loader->LoadImageAsync( 405 extension, 406 IconsInfo::GetIconResource(extension, 407 extension_misc::EXTENSION_ICON_SMALLISH, 408 ExtensionIconSet::MATCH_EXACTLY), 409 gfx::Size(extension_misc::EXTENSION_ICON_SMALLISH, 410 extension_misc::EXTENSION_ICON_SMALLISH), 411 base::Bind(&TabHelper::OnImageLoaded, 412 image_loader_ptr_factory_.GetWeakPtr())); 413 } 414} 415 416void TabHelper::SetAppIcon(const SkBitmap& app_icon) { 417 extension_app_icon_ = app_icon; 418 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE); 419} 420 421void TabHelper::SetWebstoreInlineInstallerFactoryForTests( 422 WebstoreInlineInstallerFactory* factory) { 423 webstore_inline_installer_factory_.reset(factory); 424} 425 426void TabHelper::OnImageLoaded(const gfx::Image& image) { 427 if (!image.IsEmpty()) { 428 extension_app_icon_ = *image.ToSkBitmap(); 429 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); 430 } 431} 432 433WindowController* TabHelper::GetExtensionWindowController() const { 434 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents()); 435} 436 437void TabHelper::OnInlineInstallComplete(int install_id, 438 int return_route_id, 439 bool success, 440 const std::string& error) { 441 Send(new ExtensionMsg_InlineWebstoreInstallResponse( 442 return_route_id, install_id, success, success ? std::string() : error)); 443} 444 445WebContents* TabHelper::GetAssociatedWebContents() const { 446 return web_contents(); 447} 448 449void TabHelper::GetApplicationInfo(int32 page_id) { 450 Send(new ExtensionMsg_GetApplicationInfo(routing_id(), page_id)); 451} 452 453void TabHelper::Observe(int type, 454 const content::NotificationSource& source, 455 const content::NotificationDetails& details) { 456 switch (type) { 457 case content::NOTIFICATION_LOAD_STOP: { 458 const NavigationController& controller = 459 *content::Source<NavigationController>(source).ptr(); 460 DCHECK_EQ(controller.GetWebContents(), web_contents()); 461 462 if (pending_web_app_action_ == UPDATE_SHORTCUT) { 463 // Schedule a shortcut update when web application info is available if 464 // last committed entry is not NULL. Last committed entry could be NULL 465 // when an interstitial page is injected (e.g. bad https certificate, 466 // malware site etc). When this happens, we abort the shortcut update. 467 NavigationEntry* entry = controller.GetLastCommittedEntry(); 468 if (entry) 469 GetApplicationInfo(entry->GetPageID()); 470 else 471 pending_web_app_action_ = NONE; 472 } 473 break; 474 } 475 476 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 477 if (script_bubble_controller_) { 478 script_bubble_controller_->OnExtensionUnloaded( 479 content::Details<extensions::UnloadedExtensionInfo>( 480 details)->extension->id()); 481 break; 482 } 483 } 484 } 485} 486 487void TabHelper::SetTabId(RenderViewHost* render_view_host) { 488 render_view_host->Send( 489 new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(), 490 SessionID::IdForTab(web_contents()))); 491} 492 493} // namespace extensions 494