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