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