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.
5 #include "chrome/browser/extensions/tab_helper.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/activity_log/activity_log.h"
13 #include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
14 #include "chrome/browser/extensions/api/declarative_content/content_rules_registry.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/crx_installer.h"
18 #include "chrome/browser/extensions/error_console/error_console.h"
19 #include "chrome/browser/extensions/extension_action.h"
20 #include "chrome/browser/extensions/extension_action_manager.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_tab_util.h"
23 #include "chrome/browser/extensions/image_loader.h"
24 #include "chrome/browser/extensions/page_action_controller.h"
25 #include "chrome/browser/extensions/script_executor.h"
26 #include "chrome/browser/extensions/webstore_inline_installer.h"
27 #include "chrome/browser/extensions/webstore_inline_installer_factory.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/sessions/session_id.h"
30 #include "chrome/browser/sessions/session_tab_helper.h"
31 #include "chrome/browser/shell_integration.h"
32 #include "chrome/browser/ui/browser_commands.h"
33 #include "chrome/browser/ui/browser_dialogs.h"
34 #include "chrome/browser/ui/browser_finder.h"
35 #include "chrome/browser/ui/browser_window.h"
36 #include "chrome/browser/ui/host_desktop.h"
37 #include "chrome/browser/ui/web_applications/web_app_ui.h"
38 #include "chrome/browser/web_applications/web_app.h"
39 #include "chrome/common/chrome_switches.h"
40 #include "chrome/common/extensions/chrome_extension_messages.h"
41 #include "chrome/common/extensions/extension_constants.h"
42 #include "chrome/common/extensions/extension_icon_set.h"
43 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
44 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
45 #include "chrome/common/render_messages.h"
46 #include "chrome/common/url_constants.h"
47 #include "content/public/browser/invalidate_type.h"
48 #include "content/public/browser/navigation_controller.h"
49 #include "content/public/browser/navigation_details.h"
50 #include "content/public/browser/navigation_entry.h"
51 #include "content/public/browser/notification_service.h"
52 #include "content/public/browser/notification_source.h"
53 #include "content/public/browser/notification_types.h"
54 #include "content/public/browser/render_process_host.h"
55 #include "content/public/browser/render_view_host.h"
56 #include "content/public/browser/render_widget_host_view.h"
57 #include "content/public/browser/web_contents.h"
58 #include "content/public/browser/web_contents_view.h"
59 #include "content/public/common/frame_navigate_params.h"
60 #include "extensions/browser/extension_error.h"
61 #include "extensions/browser/extension_registry.h"
62 #include "extensions/browser/extension_system.h"
63 #include "extensions/common/extension.h"
64 #include "extensions/common/extension_messages.h"
65 #include "extensions/common/extension_resource.h"
66 #include "extensions/common/extension_urls.h"
67 #include "extensions/common/feature_switch.h"
69 #if defined(OS_CHROMEOS)
70 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
73 using content::NavigationController;
74 using content::NavigationEntry;
75 using content::RenderViewHost;
76 using content::WebContents;
78 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
80 namespace extensions {
82 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
83 TabHelper* tab_helper)
84 : tab_helper_(tab_helper) {
85 tab_helper_->AddScriptExecutionObserver(this);
88 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
92 TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
94 tab_helper_->RemoveScriptExecutionObserver(this);
97 TabHelper::TabHelper(content::WebContents* web_contents)
98 : content::WebContentsObserver(web_contents),
100 extension_function_dispatcher_(
101 Profile::FromBrowserContext(web_contents->GetBrowserContext()), this),
102 pending_web_app_action_(NONE),
103 script_executor_(new ScriptExecutor(web_contents,
104 &script_execution_observers_)),
105 location_bar_controller_(new PageActionController(web_contents)),
106 image_loader_ptr_factory_(this),
107 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) {
108 // The ActiveTabPermissionManager requires a session ID; ensure this
109 // WebContents has one.
110 SessionTabHelper::CreateForWebContents(web_contents);
111 if (web_contents->GetRenderViewHost())
112 SetTabId(web_contents->GetRenderViewHost());
113 active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
115 SessionID::IdForTab(web_contents),
116 Profile::FromBrowserContext(web_contents->GetBrowserContext())));
118 // If more classes need to listen to global content script activity, then
119 // a separate routing class with an observer interface should be written.
120 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
122 #if defined(ENABLE_EXTENSIONS)
123 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
127 content::NOTIFICATION_LOAD_STOP,
128 content::Source<NavigationController>(
129 &web_contents->GetController()));
132 TabHelper::~TabHelper() {
133 #if defined(ENABLE_EXTENSIONS)
134 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
138 void TabHelper::CreateApplicationShortcuts() {
139 DCHECK(CanCreateApplicationShortcuts());
140 NavigationEntry* entry =
141 web_contents()->GetController().GetLastCommittedEntry();
145 pending_web_app_action_ = CREATE_SHORTCUT;
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());
152 void TabHelper::CreateHostedAppFromWebContents() {
153 DCHECK(CanCreateApplicationShortcuts());
154 NavigationEntry* entry =
155 web_contents()->GetController().GetLastCommittedEntry();
159 pending_web_app_action_ = CREATE_HOSTED_APP;
161 // Start fetching web app info for CreateApplicationShortcut dialog and show
162 // the dialog when the data is available in OnDidGetApplicationInfo.
163 GetApplicationInfo(entry->GetPageID());
166 bool TabHelper::CanCreateApplicationShortcuts() const {
167 #if defined(OS_MACOSX)
170 return web_app::IsValidUrl(web_contents()->GetURL()) &&
171 pending_web_app_action_ == NONE;
175 void TabHelper::SetExtensionApp(const Extension* extension) {
176 DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
177 if (extension_app_ == extension)
180 extension_app_ = extension;
182 UpdateExtensionAppIcon(extension_app_);
184 content::NotificationService::current()->Notify(
185 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
186 content::Source<TabHelper>(this),
187 content::NotificationService::NoDetails());
190 void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
191 const Extension* extension = GetExtension(extension_app_id);
193 SetExtensionApp(extension);
196 void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
197 const Extension* extension = GetExtension(extension_app_id);
199 UpdateExtensionAppIcon(extension);
202 SkBitmap* TabHelper::GetExtensionAppIcon() {
203 if (extension_app_icon_.empty())
206 return &extension_app_icon_;
209 void TabHelper::FinishCreateBookmarkApp(
210 const extensions::Extension* extension,
211 const WebApplicationInfo& web_app_info) {
212 pending_web_app_action_ = NONE;
214 // There was an error with downloading the icons or installing the app.
218 #if defined(OS_CHROMEOS)
219 ChromeLauncherController::instance()->PinAppWithID(extension->id());
222 // Android does not implement browser_finder.cc.
223 #if !defined(OS_ANDROID)
224 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
226 browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id());
231 void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
232 SetTabId(render_view_host);
235 void TabHelper::DidNavigateMainFrame(
236 const content::LoadCommittedDetails& details,
237 const content::FrameNavigateParams& params) {
238 #if defined(ENABLE_EXTENSIONS)
239 if (ExtensionSystem::Get(profile_)->extension_service() &&
240 RulesRegistryService::Get(profile_)) {
241 RulesRegistryService::Get(profile_)->content_rules_registry()->
242 DidNavigateMainFrame(web_contents(), details, params);
244 #endif // defined(ENABLE_EXTENSIONS)
247 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
248 ExtensionService* service = profile->GetExtensionService();
252 if (CommandLine::ForCurrentProcess()->HasSwitch(
253 switches::kEnableStreamlinedHostedApps)) {
254 #if !defined(OS_ANDROID)
255 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
256 if (browser && browser->is_app()) {
257 SetExtensionApp(service->GetInstalledExtension(
258 web_app::GetExtensionIdFromApplicationName(browser->app_name())));
260 UpdateExtensionAppIcon(service->GetInstalledExtensionByUrl(params.url));
264 UpdateExtensionAppIcon(service->GetInstalledExtensionByUrl(params.url));
267 if (details.is_in_page)
270 ExtensionActionManager* extension_action_manager =
271 ExtensionActionManager::Get(profile);
272 for (ExtensionSet::const_iterator it = service->extensions()->begin();
273 it != service->extensions()->end(); ++it) {
274 ExtensionAction* browser_action =
275 extension_action_manager->GetBrowserAction(*it->get());
276 if (browser_action) {
277 browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
278 content::NotificationService::current()->Notify(
279 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
280 content::Source<ExtensionAction>(browser_action),
281 content::NotificationService::NoDetails());
286 bool TabHelper::OnMessageReceived(const IPC::Message& message) {
288 IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
289 IPC_MESSAGE_HANDLER(ChromeExtensionHostMsg_DidGetApplicationInfo,
290 OnDidGetApplicationInfo)
291 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
292 OnInlineWebstoreInstall)
293 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
294 OnGetAppInstallState);
295 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
296 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
297 OnContentScriptsExecuting)
298 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
300 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
301 OnDetailedConsoleMessageAdded)
302 IPC_MESSAGE_UNHANDLED(handled = false)
303 IPC_END_MESSAGE_MAP()
307 void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
308 WebContents* new_web_contents) {
309 // When the WebContents that this is attached to is cloned, give the new clone
310 // a TabHelper and copy state over.
311 CreateForWebContents(new_web_contents);
312 TabHelper* new_helper = FromWebContents(new_web_contents);
314 new_helper->SetExtensionApp(extension_app());
315 new_helper->extension_app_icon_ = extension_app_icon_;
318 void TabHelper::OnDidGetApplicationInfo(int32 page_id,
319 const WebApplicationInfo& info) {
320 // Android does not implement BrowserWindow.
321 #if !defined(OS_MACOSX) && !defined(OS_ANDROID)
322 web_app_info_ = info;
324 NavigationEntry* entry =
325 web_contents()->GetController().GetLastCommittedEntry();
326 if (!entry || (entry->GetPageID() != page_id))
329 switch (pending_web_app_action_) {
330 case CREATE_SHORTCUT: {
331 chrome::ShowCreateWebAppShortcutsDialog(
332 web_contents()->GetView()->GetTopLevelNativeWindow(),
336 case CREATE_HOSTED_APP: {
337 if (web_app_info_.app_url.is_empty())
338 web_app_info_.app_url = web_contents()->GetURL();
340 if (web_app_info_.title.empty())
341 web_app_info_.title = web_contents()->GetTitle();
342 if (web_app_info_.title.empty())
343 web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());
345 bookmark_app_helper_.reset(new BookmarkAppHelper(
346 profile_->GetExtensionService(), web_app_info_, web_contents()));
347 bookmark_app_helper_->Create(base::Bind(
348 &TabHelper::FinishCreateBookmarkApp, base::Unretained(this)));
351 case UPDATE_SHORTCUT: {
352 web_app::UpdateShortcutForTabContents(web_contents());
360 // The hosted app action will be cleared once the installation completes or
362 if (pending_web_app_action_ != CREATE_HOSTED_APP)
363 pending_web_app_action_ = NONE;
367 void TabHelper::OnInlineWebstoreInstall(int install_id,
369 const std::string& webstore_item_id,
370 const GURL& requestor_url,
371 int listeners_mask) {
372 #if defined(ENABLE_EXTENSIONS)
373 // Check that the listener is reasonable. We should never get anything other
374 // than an install stage listener, a download listener, or both.
375 if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
376 api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) {
380 // Inform the Webstore API that an inline install is happening, in case the
381 // page requested status updates.
383 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
384 WebstoreAPI::Get(profile)->OnInlineInstallStart(
385 return_route_id, this, webstore_item_id, listeners_mask);
388 WebstoreStandaloneInstaller::Callback callback =
389 base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this),
390 install_id, return_route_id);
391 scoped_refptr<WebstoreInlineInstaller> installer(
392 webstore_inline_installer_factory_->CreateInstaller(
397 installer->BeginInstall();
400 void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
403 ExtensionRegistry* registry =
404 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
405 const ExtensionSet& extensions = registry->enabled_extensions();
406 const ExtensionSet& disabled_extensions = registry->disabled_extensions();
409 if (extensions.GetHostedAppByURL(requestor_url))
410 state = extension_misc::kAppStateInstalled;
411 else if (disabled_extensions.GetHostedAppByURL(requestor_url))
412 state = extension_misc::kAppStateDisabled;
414 state = extension_misc::kAppStateNotInstalled;
416 Send(new ExtensionMsg_GetAppInstallStateResponse(
417 return_route_id, state, callback_id));
420 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
421 extension_function_dispatcher_.Dispatch(request,
422 web_contents()->GetRenderViewHost());
425 void TabHelper::OnContentScriptsExecuting(
426 const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
428 const GURL& on_url) {
429 FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_,
430 OnScriptsExecuted(web_contents(),
431 executing_scripts_map,
436 void TabHelper::OnWatchedPageChange(
437 const std::vector<std::string>& css_selectors) {
438 #if defined(ENABLE_EXTENSIONS)
439 if (ExtensionSystem::Get(profile_)->extension_service() &&
440 RulesRegistryService::Get(profile_)) {
441 RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
442 web_contents(), css_selectors);
444 #endif // defined(ENABLE_EXTENSIONS)
447 void TabHelper::OnDetailedConsoleMessageAdded(
448 const base::string16& message,
449 const base::string16& source,
450 const StackTrace& stack_trace,
451 int32 severity_level) {
452 if (IsSourceFromAnExtension(source)) {
453 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
454 ErrorConsole::Get(profile_)->ReportError(
455 scoped_ptr<ExtensionError>(new RuntimeError(
456 extension_app_ ? extension_app_->id() : std::string(),
457 profile_->IsOffTheRecord(),
462 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
463 static_cast<logging::LogSeverity>(severity_level),
465 rvh->GetProcess()->GetID())));
469 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
470 if (extension_app_id.empty())
474 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
475 ExtensionService* extension_service = profile->GetExtensionService();
476 if (!extension_service || !extension_service->is_ready())
479 const Extension* extension =
480 extension_service->GetExtensionById(extension_app_id, false);
484 void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
485 extension_app_icon_.reset();
486 // Ensure previously enqueued callbacks are ignored.
487 image_loader_ptr_factory_.InvalidateWeakPtrs();
489 // Enqueue OnImageLoaded callback.
492 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
493 extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile);
494 loader->LoadImageAsync(
496 IconsInfo::GetIconResource(extension,
497 extension_misc::EXTENSION_ICON_SMALL,
498 ExtensionIconSet::MATCH_BIGGER),
499 gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
500 extension_misc::EXTENSION_ICON_SMALL),
501 base::Bind(&TabHelper::OnImageLoaded,
502 image_loader_ptr_factory_.GetWeakPtr()));
506 void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
507 extension_app_icon_ = app_icon;
508 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
511 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
512 WebstoreInlineInstallerFactory* factory) {
513 webstore_inline_installer_factory_.reset(factory);
516 void TabHelper::OnImageLoaded(const gfx::Image& image) {
517 if (!image.IsEmpty()) {
518 extension_app_icon_ = *image.ToSkBitmap();
519 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
523 WindowController* TabHelper::GetExtensionWindowController() const {
524 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
527 void TabHelper::OnInlineInstallComplete(int install_id,
530 const std::string& error) {
531 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
532 return_route_id, install_id, success, success ? std::string() : error));
535 WebContents* TabHelper::GetAssociatedWebContents() const {
536 return web_contents();
539 void TabHelper::GetApplicationInfo(int32 page_id) {
540 Send(new ChromeExtensionMsg_GetApplicationInfo(routing_id(), page_id));
543 void TabHelper::Observe(int type,
544 const content::NotificationSource& source,
545 const content::NotificationDetails& details) {
547 case content::NOTIFICATION_LOAD_STOP: {
548 const NavigationController& controller =
549 *content::Source<NavigationController>(source).ptr();
550 DCHECK_EQ(controller.GetWebContents(), web_contents());
552 if (pending_web_app_action_ == UPDATE_SHORTCUT) {
553 // Schedule a shortcut update when web application info is available if
554 // last committed entry is not NULL. Last committed entry could be NULL
555 // when an interstitial page is injected (e.g. bad https certificate,
556 // malware site etc). When this happens, we abort the shortcut update.
557 NavigationEntry* entry = controller.GetLastCommittedEntry();
559 GetApplicationInfo(entry->GetPageID());
561 pending_web_app_action_ = NONE;
568 void TabHelper::SetTabId(RenderViewHost* render_view_host) {
569 render_view_host->Send(
570 new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
571 SessionID::IdForTab(web_contents())));
574 } // namespace extensions