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