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/error_console/error_console.h"
18 #include "chrome/browser/extensions/extension_action.h"
19 #include "chrome/browser/extensions/extension_action_manager.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_tab_util.h"
22 #include "chrome/browser/extensions/image_loader.h"
23 #include "chrome/browser/extensions/page_action_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/shell_integration.h"
31 #include "chrome/browser/ui/browser_commands.h"
32 #include "chrome/browser/ui/browser_dialogs.h"
33 #include "chrome/browser/ui/browser_finder.h"
34 #include "chrome/browser/ui/browser_window.h"
35 #include "chrome/browser/ui/host_desktop.h"
36 #include "chrome/browser/web_applications/web_app.h"
37 #include "chrome/common/chrome_switches.h"
38 #include "chrome/common/extensions/chrome_extension_messages.h"
39 #include "chrome/common/extensions/extension_constants.h"
40 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
41 #include "chrome/common/render_messages.h"
42 #include "chrome/common/url_constants.h"
43 #include "content/public/browser/invalidate_type.h"
44 #include "content/public/browser/navigation_controller.h"
45 #include "content/public/browser/navigation_details.h"
46 #include "content/public/browser/navigation_entry.h"
47 #include "content/public/browser/notification_service.h"
48 #include "content/public/browser/notification_source.h"
49 #include "content/public/browser/notification_types.h"
50 #include "content/public/browser/render_process_host.h"
51 #include "content/public/browser/render_view_host.h"
52 #include "content/public/browser/render_widget_host_view.h"
53 #include "content/public/browser/web_contents.h"
54 #include "content/public/common/frame_navigate_params.h"
55 #include "extensions/browser/extension_error.h"
56 #include "extensions/browser/extension_registry.h"
57 #include "extensions/browser/extension_system.h"
58 #include "extensions/common/extension.h"
59 #include "extensions/common/extension_icon_set.h"
60 #include "extensions/common/extension_messages.h"
61 #include "extensions/common/extension_resource.h"
62 #include "extensions/common/extension_urls.h"
63 #include "extensions/common/feature_switch.h"
64 #include "extensions/common/manifest_handlers/icons_handler.h"
66 #if defined(OS_CHROMEOS)
67 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
71 #include "chrome/browser/web_applications/web_app_win.h"
74 using content::NavigationController
;
75 using content::NavigationEntry
;
76 using content::RenderViewHost
;
77 using content::WebContents
;
79 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper
);
81 namespace extensions
{
83 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
84 TabHelper
* tab_helper
)
85 : tab_helper_(tab_helper
) {
86 tab_helper_
->AddScriptExecutionObserver(this);
89 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
93 TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
95 tab_helper_
->RemoveScriptExecutionObserver(this);
98 TabHelper::TabHelper(content::WebContents
* web_contents
)
99 : content::WebContentsObserver(web_contents
),
100 extension_app_(NULL
),
101 extension_function_dispatcher_(
102 Profile::FromBrowserContext(web_contents
->GetBrowserContext()), this),
103 pending_web_app_action_(NONE
),
104 script_executor_(new ScriptExecutor(web_contents
,
105 &script_execution_observers_
)),
106 location_bar_controller_(new PageActionController(web_contents
)),
107 image_loader_ptr_factory_(this),
108 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) {
109 // The ActiveTabPermissionManager requires a session ID; ensure this
110 // WebContents has one.
111 SessionTabHelper::CreateForWebContents(web_contents
);
112 if (web_contents
->GetRenderViewHost())
113 SetTabId(web_contents
->GetRenderViewHost());
114 active_tab_permission_granter_
.reset(new ActiveTabPermissionGranter(
116 SessionID::IdForTab(web_contents
),
117 Profile::FromBrowserContext(web_contents
->GetBrowserContext())));
119 // If more classes need to listen to global content script activity, then
120 // a separate routing class with an observer interface should be written.
121 profile_
= Profile::FromBrowserContext(web_contents
->GetBrowserContext());
123 #if defined(ENABLE_EXTENSIONS)
124 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_
));
128 content::NOTIFICATION_LOAD_STOP
,
129 content::Source
<NavigationController
>(
130 &web_contents
->GetController()));
133 TabHelper::~TabHelper() {
134 #if defined(ENABLE_EXTENSIONS)
135 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_
));
139 void TabHelper::CreateApplicationShortcuts() {
140 DCHECK(CanCreateApplicationShortcuts());
141 NavigationEntry
* entry
=
142 web_contents()->GetController().GetLastCommittedEntry();
146 pending_web_app_action_
= CREATE_SHORTCUT
;
148 // Start fetching web app info for CreateApplicationShortcut dialog and show
149 // the dialog when the data is available in OnDidGetApplicationInfo.
150 GetApplicationInfo(entry
->GetPageID());
153 void TabHelper::CreateHostedAppFromWebContents() {
154 DCHECK(CanCreateBookmarkApp());
155 NavigationEntry
* entry
=
156 web_contents()->GetController().GetLastCommittedEntry();
160 pending_web_app_action_
= CREATE_HOSTED_APP
;
162 // Start fetching web app info for CreateApplicationShortcut dialog and show
163 // the dialog when the data is available in OnDidGetApplicationInfo.
164 GetApplicationInfo(entry
->GetPageID());
167 bool TabHelper::CanCreateApplicationShortcuts() const {
168 #if defined(OS_MACOSX)
171 return web_app::IsValidUrl(web_contents()->GetURL()) &&
172 pending_web_app_action_
== NONE
;
176 bool TabHelper::CanCreateBookmarkApp() const {
177 #if defined(OS_MACOSX)
180 return IsValidBookmarkAppUrl(web_contents()->GetURL()) &&
181 pending_web_app_action_
== NONE
;
185 void TabHelper::SetExtensionApp(const Extension
* extension
) {
186 DCHECK(!extension
|| AppLaunchInfo::GetFullLaunchURL(extension
).is_valid());
187 if (extension_app_
== extension
)
190 extension_app_
= extension
;
192 UpdateExtensionAppIcon(extension_app_
);
194 content::NotificationService::current()->Notify(
195 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
,
196 content::Source
<TabHelper
>(this),
197 content::NotificationService::NoDetails());
200 void TabHelper::SetExtensionAppById(const std::string
& extension_app_id
) {
201 const Extension
* extension
= GetExtension(extension_app_id
);
203 SetExtensionApp(extension
);
206 void TabHelper::SetExtensionAppIconById(const std::string
& extension_app_id
) {
207 const Extension
* extension
= GetExtension(extension_app_id
);
209 UpdateExtensionAppIcon(extension
);
212 SkBitmap
* TabHelper::GetExtensionAppIcon() {
213 if (extension_app_icon_
.empty())
216 return &extension_app_icon_
;
219 void TabHelper::FinishCreateBookmarkApp(
220 const extensions::Extension
* extension
,
221 const WebApplicationInfo
& web_app_info
) {
222 pending_web_app_action_
= NONE
;
224 // There was an error with downloading the icons or installing the app.
228 #if defined(OS_CHROMEOS)
229 ChromeLauncherController::instance()->PinAppWithID(extension
->id());
232 // Android does not implement browser_finder.cc.
233 #if !defined(OS_ANDROID)
234 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
236 browser
->window()->ShowBookmarkAppBubble(web_app_info
, extension
->id());
241 void TabHelper::RenderViewCreated(RenderViewHost
* render_view_host
) {
242 SetTabId(render_view_host
);
245 void TabHelper::DidNavigateMainFrame(
246 const content::LoadCommittedDetails
& details
,
247 const content::FrameNavigateParams
& params
) {
248 #if defined(ENABLE_EXTENSIONS)
249 if (ExtensionSystem::Get(profile_
)->extension_service() &&
250 RulesRegistryService::Get(profile_
)) {
251 RulesRegistryService::Get(profile_
)->content_rules_registry()->
252 DidNavigateMainFrame(web_contents(), details
, params
);
254 #endif // defined(ENABLE_EXTENSIONS)
256 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
257 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context
);
258 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
260 if (CommandLine::ForCurrentProcess()->HasSwitch(
261 switches::kEnableStreamlinedHostedApps
)) {
262 #if !defined(OS_ANDROID)
263 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
264 if (browser
&& browser
->is_app()) {
265 SetExtensionApp(registry
->GetExtensionById(
266 web_app::GetExtensionIdFromApplicationName(browser
->app_name()),
267 ExtensionRegistry::EVERYTHING
));
269 UpdateExtensionAppIcon(
270 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
274 UpdateExtensionAppIcon(
275 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
278 if (details
.is_in_page
)
281 ExtensionActionManager
* extension_action_manager
=
282 ExtensionActionManager::Get(Profile::FromBrowserContext(context
));
283 for (ExtensionSet::const_iterator it
= enabled_extensions
.begin();
284 it
!= enabled_extensions
.end();
286 ExtensionAction
* browser_action
=
287 extension_action_manager
->GetBrowserAction(*it
->get());
288 if (browser_action
) {
289 browser_action
->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
290 content::NotificationService::current()->Notify(
291 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED
,
292 content::Source
<ExtensionAction
>(browser_action
),
293 content::NotificationService::NoDetails());
298 bool TabHelper::OnMessageReceived(const IPC::Message
& message
) {
300 IPC_BEGIN_MESSAGE_MAP(TabHelper
, message
)
301 IPC_MESSAGE_HANDLER(ChromeExtensionHostMsg_DidGetApplicationInfo
,
302 OnDidGetApplicationInfo
)
303 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall
,
304 OnInlineWebstoreInstall
)
305 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState
,
306 OnGetAppInstallState
);
307 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request
, OnRequest
)
308 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting
,
309 OnContentScriptsExecuting
)
310 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange
,
312 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded
,
313 OnDetailedConsoleMessageAdded
)
314 IPC_MESSAGE_UNHANDLED(handled
= false)
315 IPC_END_MESSAGE_MAP()
319 void TabHelper::DidCloneToNewWebContents(WebContents
* old_web_contents
,
320 WebContents
* new_web_contents
) {
321 // When the WebContents that this is attached to is cloned, give the new clone
322 // a TabHelper and copy state over.
323 CreateForWebContents(new_web_contents
);
324 TabHelper
* new_helper
= FromWebContents(new_web_contents
);
326 new_helper
->SetExtensionApp(extension_app());
327 new_helper
->extension_app_icon_
= extension_app_icon_
;
330 void TabHelper::OnDidGetApplicationInfo(int32 page_id
,
331 const WebApplicationInfo
& info
) {
332 // Android does not implement BrowserWindow.
333 #if !defined(OS_MACOSX) && !defined(OS_ANDROID)
334 web_app_info_
= info
;
336 NavigationEntry
* entry
=
337 web_contents()->GetController().GetLastCommittedEntry();
338 if (!entry
|| (entry
->GetPageID() != page_id
))
341 switch (pending_web_app_action_
) {
342 case CREATE_SHORTCUT
: {
343 chrome::ShowCreateWebAppShortcutsDialog(
344 web_contents()->GetTopLevelNativeWindow(),
348 case CREATE_HOSTED_APP
: {
349 if (web_app_info_
.app_url
.is_empty())
350 web_app_info_
.app_url
= web_contents()->GetURL();
352 if (web_app_info_
.title
.empty())
353 web_app_info_
.title
= web_contents()->GetTitle();
354 if (web_app_info_
.title
.empty())
355 web_app_info_
.title
= base::UTF8ToUTF16(web_app_info_
.app_url
.spec());
357 bookmark_app_helper_
.reset(new BookmarkAppHelper(
358 profile_
->GetExtensionService(), web_app_info_
, web_contents()));
359 bookmark_app_helper_
->Create(base::Bind(
360 &TabHelper::FinishCreateBookmarkApp
, base::Unretained(this)));
363 case UPDATE_SHORTCUT
: {
364 web_app::UpdateShortcutForTabContents(web_contents());
372 // The hosted app action will be cleared once the installation completes or
374 if (pending_web_app_action_
!= CREATE_HOSTED_APP
)
375 pending_web_app_action_
= NONE
;
379 void TabHelper::OnInlineWebstoreInstall(int install_id
,
381 const std::string
& webstore_item_id
,
382 const GURL
& requestor_url
,
383 int listeners_mask
) {
384 #if defined(ENABLE_EXTENSIONS)
385 // Check that the listener is reasonable. We should never get anything other
386 // than an install stage listener, a download listener, or both.
387 if ((listeners_mask
& ~(api::webstore::INSTALL_STAGE_LISTENER
|
388 api::webstore::DOWNLOAD_PROGRESS_LISTENER
)) != 0) {
392 // Inform the Webstore API that an inline install is happening, in case the
393 // page requested status updates.
395 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
396 WebstoreAPI::Get(profile
)->OnInlineInstallStart(
397 return_route_id
, this, webstore_item_id
, listeners_mask
);
400 WebstoreStandaloneInstaller::Callback callback
=
401 base::Bind(&TabHelper::OnInlineInstallComplete
, base::Unretained(this),
402 install_id
, return_route_id
);
403 scoped_refptr
<WebstoreInlineInstaller
> installer(
404 webstore_inline_installer_factory_
->CreateInstaller(
409 installer
->BeginInstall();
412 void TabHelper::OnGetAppInstallState(const GURL
& requestor_url
,
415 ExtensionRegistry
* registry
=
416 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
417 const ExtensionSet
& extensions
= registry
->enabled_extensions();
418 const ExtensionSet
& disabled_extensions
= registry
->disabled_extensions();
421 if (extensions
.GetHostedAppByURL(requestor_url
))
422 state
= extension_misc::kAppStateInstalled
;
423 else if (disabled_extensions
.GetHostedAppByURL(requestor_url
))
424 state
= extension_misc::kAppStateDisabled
;
426 state
= extension_misc::kAppStateNotInstalled
;
428 Send(new ExtensionMsg_GetAppInstallStateResponse(
429 return_route_id
, state
, callback_id
));
432 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params
& request
) {
433 extension_function_dispatcher_
.Dispatch(request
,
434 web_contents()->GetRenderViewHost());
437 void TabHelper::OnContentScriptsExecuting(
438 const ScriptExecutionObserver::ExecutingScriptsMap
& executing_scripts_map
,
440 const GURL
& on_url
) {
441 FOR_EACH_OBSERVER(ScriptExecutionObserver
, script_execution_observers_
,
442 OnScriptsExecuted(web_contents(),
443 executing_scripts_map
,
448 void TabHelper::OnWatchedPageChange(
449 const std::vector
<std::string
>& css_selectors
) {
450 #if defined(ENABLE_EXTENSIONS)
451 if (ExtensionSystem::Get(profile_
)->extension_service() &&
452 RulesRegistryService::Get(profile_
)) {
453 RulesRegistryService::Get(profile_
)->content_rules_registry()->Apply(
454 web_contents(), css_selectors
);
456 #endif // defined(ENABLE_EXTENSIONS)
459 void TabHelper::OnDetailedConsoleMessageAdded(
460 const base::string16
& message
,
461 const base::string16
& source
,
462 const StackTrace
& stack_trace
,
463 int32 severity_level
) {
464 if (IsSourceFromAnExtension(source
)) {
465 content::RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
466 ErrorConsole::Get(profile_
)->ReportError(
467 scoped_ptr
<ExtensionError
>(new RuntimeError(
468 extension_app_
? extension_app_
->id() : std::string(),
469 profile_
->IsOffTheRecord(),
474 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
475 static_cast<logging::LogSeverity
>(severity_level
),
477 rvh
->GetProcess()->GetID())));
481 const Extension
* TabHelper::GetExtension(const std::string
& extension_app_id
) {
482 if (extension_app_id
.empty())
486 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
487 ExtensionService
* extension_service
= profile
->GetExtensionService();
488 if (!extension_service
|| !extension_service
->is_ready())
491 const Extension
* extension
=
492 extension_service
->GetExtensionById(extension_app_id
, false);
496 void TabHelper::UpdateExtensionAppIcon(const Extension
* extension
) {
497 extension_app_icon_
.reset();
498 // Ensure previously enqueued callbacks are ignored.
499 image_loader_ptr_factory_
.InvalidateWeakPtrs();
501 // Enqueue OnImageLoaded callback.
504 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
505 extensions::ImageLoader
* loader
= extensions::ImageLoader::Get(profile
);
506 loader
->LoadImageAsync(
508 IconsInfo::GetIconResource(extension
,
509 extension_misc::EXTENSION_ICON_SMALL
,
510 ExtensionIconSet::MATCH_BIGGER
),
511 gfx::Size(extension_misc::EXTENSION_ICON_SMALL
,
512 extension_misc::EXTENSION_ICON_SMALL
),
513 base::Bind(&TabHelper::OnImageLoaded
,
514 image_loader_ptr_factory_
.GetWeakPtr()));
518 void TabHelper::SetAppIcon(const SkBitmap
& app_icon
) {
519 extension_app_icon_
= app_icon
;
520 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE
);
523 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
524 WebstoreInlineInstallerFactory
* factory
) {
525 webstore_inline_installer_factory_
.reset(factory
);
528 void TabHelper::OnImageLoaded(const gfx::Image
& image
) {
529 if (!image
.IsEmpty()) {
530 extension_app_icon_
= *image
.ToSkBitmap();
531 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB
);
535 WindowController
* TabHelper::GetExtensionWindowController() const {
536 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
539 void TabHelper::OnInlineInstallComplete(int install_id
,
542 const std::string
& error
) {
543 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
544 return_route_id
, install_id
, success
, success
? std::string() : error
));
547 WebContents
* TabHelper::GetAssociatedWebContents() const {
548 return web_contents();
551 void TabHelper::GetApplicationInfo(int32 page_id
) {
552 Send(new ChromeExtensionMsg_GetApplicationInfo(routing_id(), page_id
));
555 void TabHelper::Observe(int type
,
556 const content::NotificationSource
& source
,
557 const content::NotificationDetails
& details
) {
559 case content::NOTIFICATION_LOAD_STOP
: {
560 const NavigationController
& controller
=
561 *content::Source
<NavigationController
>(source
).ptr();
562 DCHECK_EQ(controller
.GetWebContents(), web_contents());
564 if (pending_web_app_action_
== UPDATE_SHORTCUT
) {
565 // Schedule a shortcut update when web application info is available if
566 // last committed entry is not NULL. Last committed entry could be NULL
567 // when an interstitial page is injected (e.g. bad https certificate,
568 // malware site etc). When this happens, we abort the shortcut update.
569 NavigationEntry
* entry
= controller
.GetLastCommittedEntry();
571 GetApplicationInfo(entry
->GetPageID());
573 pending_web_app_action_
= NONE
;
580 void TabHelper::SetTabId(RenderViewHost
* render_view_host
) {
581 render_view_host
->Send(
582 new ExtensionMsg_SetTabId(render_view_host
->GetRoutingID(),
583 SessionID::IdForTab(web_contents())));
586 } // namespace extensions