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/logging.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/active_script_controller.h"
12 #include "chrome/browser/extensions/activity_log/activity_log.h"
13 #include "chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h"
14 #include "chrome/browser/extensions/api/extension_action/extension_action_api.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_tab_util.h"
19 #include "chrome/browser/extensions/extension_util.h"
20 #include "chrome/browser/extensions/location_bar_controller.h"
21 #include "chrome/browser/extensions/webstore_inline_installer.h"
22 #include "chrome/browser/extensions/webstore_inline_installer_factory.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/sessions/session_tab_helper.h"
25 #include "chrome/browser/shell_integration.h"
26 #include "chrome/browser/ui/browser_commands.h"
27 #include "chrome/browser/ui/browser_dialogs.h"
28 #include "chrome/browser/ui/browser_finder.h"
29 #include "chrome/browser/ui/host_desktop.h"
30 #include "chrome/browser/web_applications/web_app.h"
31 #include "chrome/common/extensions/chrome_extension_messages.h"
32 #include "chrome/common/extensions/extension_constants.h"
33 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
34 #include "chrome/common/render_messages.h"
35 #include "chrome/common/url_constants.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/web_contents.h"
46 #include "content/public/common/frame_navigate_params.h"
47 #include "extensions/browser/api/declarative/rules_registry_service.h"
48 #include "extensions/browser/extension_error.h"
49 #include "extensions/browser/extension_prefs.h"
50 #include "extensions/browser/extension_registry.h"
51 #include "extensions/browser/extension_system.h"
52 #include "extensions/browser/image_loader.h"
53 #include "extensions/common/constants.h"
54 #include "extensions/common/extension.h"
55 #include "extensions/common/extension_icon_set.h"
56 #include "extensions/common/extension_messages.h"
57 #include "extensions/common/extension_resource.h"
58 #include "extensions/common/extension_urls.h"
59 #include "extensions/common/feature_switch.h"
60 #include "extensions/common/manifest_handlers/icons_handler.h"
63 #include "chrome/browser/web_applications/web_app_win.h"
66 using content::NavigationController
;
67 using content::NavigationEntry
;
68 using content::RenderViewHost
;
69 using content::WebContents
;
71 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper
);
73 namespace extensions
{
75 TabHelper::TabHelper(content::WebContents
* web_contents
)
76 : content::WebContentsObserver(web_contents
),
78 extension_function_dispatcher_(
79 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
81 pending_web_app_action_(NONE
),
82 last_committed_nav_entry_unique_id_(0),
83 update_shortcut_on_load_complete_(false),
85 new ScriptExecutor(web_contents
, &script_execution_observers_
)),
86 location_bar_controller_(new LocationBarController(web_contents
)),
87 active_script_controller_(new ActiveScriptController(web_contents
)),
88 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()),
89 image_loader_ptr_factory_(this),
90 weak_ptr_factory_(this) {
91 // The ActiveTabPermissionManager requires a session ID; ensure this
92 // WebContents has one.
93 SessionTabHelper::CreateForWebContents(web_contents
);
94 if (web_contents
->GetRenderViewHost())
95 SetTabId(web_contents
->GetRenderViewHost());
96 active_tab_permission_granter_
.reset(new ActiveTabPermissionGranter(
98 SessionTabHelper::IdForTab(web_contents
),
99 Profile::FromBrowserContext(web_contents
->GetBrowserContext())));
101 // If more classes need to listen to global content script activity, then
102 // a separate routing class with an observer interface should be written.
103 profile_
= Profile::FromBrowserContext(web_contents
->GetBrowserContext());
105 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_
));
108 content::NOTIFICATION_LOAD_STOP
,
109 content::Source
<NavigationController
>(
110 &web_contents
->GetController()));
113 TabHelper::~TabHelper() {
114 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_
));
117 void TabHelper::CreateApplicationShortcuts() {
118 DCHECK(CanCreateApplicationShortcuts());
119 if (pending_web_app_action_
!= NONE
)
122 // Start fetching web app info for CreateApplicationShortcut dialog and show
123 // the dialog when the data is available in OnDidGetApplicationInfo.
124 GetApplicationInfo(CREATE_SHORTCUT
);
127 void TabHelper::CreateHostedAppFromWebContents() {
128 DCHECK(CanCreateBookmarkApp());
129 if (pending_web_app_action_
!= NONE
)
132 // Start fetching web app info for CreateApplicationShortcut dialog and show
133 // the dialog when the data is available in OnDidGetApplicationInfo.
134 GetApplicationInfo(CREATE_HOSTED_APP
);
137 bool TabHelper::CanCreateApplicationShortcuts() const {
138 #if defined(OS_MACOSX)
141 return web_app::IsValidUrl(web_contents()->GetURL());
145 bool TabHelper::CanCreateBookmarkApp() const {
146 return !profile_
->IsGuestSession() &&
147 !profile_
->IsSystemProfile() &&
148 IsValidBookmarkAppUrl(web_contents()->GetURL());
151 void TabHelper::AddScriptExecutionObserver(ScriptExecutionObserver
* observer
) {
152 script_execution_observers_
.AddObserver(observer
);
155 void TabHelper::RemoveScriptExecutionObserver(
156 ScriptExecutionObserver
* observer
) {
157 script_execution_observers_
.RemoveObserver(observer
);
160 void TabHelper::SetExtensionApp(const Extension
* extension
) {
161 DCHECK(!extension
|| AppLaunchInfo::GetFullLaunchURL(extension
).is_valid());
162 if (extension_app_
== extension
)
165 extension_app_
= extension
;
167 UpdateExtensionAppIcon(extension_app_
);
169 content::NotificationService::current()->Notify(
170 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
,
171 content::Source
<TabHelper
>(this),
172 content::NotificationService::NoDetails());
175 void TabHelper::SetExtensionAppById(const std::string
& extension_app_id
) {
176 const Extension
* extension
= GetExtension(extension_app_id
);
178 SetExtensionApp(extension
);
181 void TabHelper::SetExtensionAppIconById(const std::string
& extension_app_id
) {
182 const Extension
* extension
= GetExtension(extension_app_id
);
184 UpdateExtensionAppIcon(extension
);
187 SkBitmap
* TabHelper::GetExtensionAppIcon() {
188 if (extension_app_icon_
.empty())
191 return &extension_app_icon_
;
194 void TabHelper::FinishCreateBookmarkApp(
195 const Extension
* extension
,
196 const WebApplicationInfo
& web_app_info
) {
197 pending_web_app_action_
= NONE
;
200 void TabHelper::RenderViewCreated(RenderViewHost
* render_view_host
) {
201 SetTabId(render_view_host
);
204 void TabHelper::DidNavigateMainFrame(
205 const content::LoadCommittedDetails
& details
,
206 const content::FrameNavigateParams
& params
) {
207 RulesRegistryService
* rules_registry_service
=
208 RulesRegistryService::Get(profile_
);
209 if (rules_registry_service
) {
210 rules_registry_service
->content_rules_registry()->
211 DidNavigateMainFrame(web_contents(), details
, params
);
212 // The original profile's content rules registry handles rules for spanning
213 // extensions in incognito profiles, so let it know about the navigation
215 if (profile_
->IsOffTheRecord()) {
216 RulesRegistryService
* incognito_rules_registry_service
=
217 RulesRegistryService::Get(profile_
->GetOriginalProfile());
218 // The content and web request rules registries depend on separate
219 // instances for original/incognito profiles. See the comment on
220 // ChromeContentRulesRegistry.
221 DCHECK_NE(rules_registry_service
, incognito_rules_registry_service
);
222 if (incognito_rules_registry_service
) {
223 incognito_rules_registry_service
->content_rules_registry()->
224 DidNavigateMainFrame(web_contents(), details
, params
);
229 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
230 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context
);
231 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
233 if (util::IsNewBookmarkAppsEnabled()) {
234 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
235 if (browser
&& browser
->is_app()) {
236 const Extension
* extension
= registry
->GetExtensionById(
237 web_app::GetExtensionIdFromApplicationName(browser
->app_name()),
238 ExtensionRegistry::EVERYTHING
);
239 if (extension
&& AppLaunchInfo::GetFullLaunchURL(extension
).is_valid())
240 SetExtensionApp(extension
);
242 UpdateExtensionAppIcon(
243 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
246 UpdateExtensionAppIcon(
247 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
250 if (!details
.is_in_page
)
251 ExtensionActionAPI::Get(context
)->ClearAllValuesForTab(web_contents());
254 bool TabHelper::OnMessageReceived(const IPC::Message
& message
) {
256 IPC_BEGIN_MESSAGE_MAP(TabHelper
, message
)
257 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo
,
258 OnDidGetWebApplicationInfo
)
259 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall
,
260 OnInlineWebstoreInstall
)
261 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState
,
262 OnGetAppInstallState
);
263 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request
, OnRequest
)
264 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting
,
265 OnContentScriptsExecuting
)
266 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange
,
268 IPC_MESSAGE_UNHANDLED(handled
= false)
269 IPC_END_MESSAGE_MAP()
273 bool TabHelper::OnMessageReceived(const IPC::Message
& message
,
274 content::RenderFrameHost
* render_frame_host
) {
276 IPC_BEGIN_MESSAGE_MAP(TabHelper
, message
)
277 IPC_MESSAGE_HANDLER(ExtensionHostMsg_DetailedConsoleMessageAdded
,
278 OnDetailedConsoleMessageAdded
)
279 IPC_MESSAGE_UNHANDLED(handled
= false)
280 IPC_END_MESSAGE_MAP()
284 void TabHelper::DidCloneToNewWebContents(WebContents
* old_web_contents
,
285 WebContents
* new_web_contents
) {
286 // When the WebContents that this is attached to is cloned, give the new clone
287 // a TabHelper and copy state over.
288 CreateForWebContents(new_web_contents
);
289 TabHelper
* new_helper
= FromWebContents(new_web_contents
);
291 new_helper
->SetExtensionApp(extension_app());
292 new_helper
->extension_app_icon_
= extension_app_icon_
;
295 void TabHelper::OnDidGetWebApplicationInfo(const WebApplicationInfo
& info
) {
296 web_app_info_
= info
;
298 NavigationEntry
* entry
=
299 web_contents()->GetController().GetLastCommittedEntry();
300 if (!entry
|| last_committed_nav_entry_unique_id_
!= entry
->GetUniqueID())
302 last_committed_nav_entry_unique_id_
= 0;
304 switch (pending_web_app_action_
) {
305 #if !defined(OS_MACOSX)
306 case CREATE_SHORTCUT
: {
307 chrome::ShowCreateWebAppShortcutsDialog(
308 web_contents()->GetTopLevelNativeWindow(),
313 case CREATE_HOSTED_APP
: {
314 if (web_app_info_
.app_url
.is_empty())
315 web_app_info_
.app_url
= web_contents()->GetURL();
317 if (web_app_info_
.title
.empty())
318 web_app_info_
.title
= web_contents()->GetTitle();
319 if (web_app_info_
.title
.empty())
320 web_app_info_
.title
= base::UTF8ToUTF16(web_app_info_
.app_url
.spec());
322 bookmark_app_helper_
.reset(
323 new BookmarkAppHelper(profile_
, web_app_info_
, web_contents()));
324 bookmark_app_helper_
->Create(base::Bind(
325 &TabHelper::FinishCreateBookmarkApp
, weak_ptr_factory_
.GetWeakPtr()));
328 case UPDATE_SHORTCUT
: {
329 web_app::UpdateShortcutForTabContents(web_contents());
337 // The hosted app action will be cleared once the installation completes or
339 if (pending_web_app_action_
!= CREATE_HOSTED_APP
)
340 pending_web_app_action_
= NONE
;
343 void TabHelper::OnInlineWebstoreInstall(int install_id
,
345 const std::string
& webstore_item_id
,
346 const GURL
& requestor_url
,
347 int listeners_mask
) {
348 // Check that the listener is reasonable. We should never get anything other
349 // than an install stage listener, a download listener, or both.
350 if ((listeners_mask
& ~(api::webstore::INSTALL_STAGE_LISTENER
|
351 api::webstore::DOWNLOAD_PROGRESS_LISTENER
)) != 0 ||
352 requestor_url
.is_empty()) {
356 // Inform the Webstore API that an inline install is happening, in case the
357 // page requested status updates.
359 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
361 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile
);
362 if (registry
->disabled_extensions().Contains(webstore_item_id
) &&
363 (ExtensionPrefs::Get(profile
)->GetDisableReasons(webstore_item_id
) &
364 Extension::DISABLE_PERMISSIONS_INCREASE
) != 0) {
365 // The extension was disabled due to permissions increase. Prompt for
367 // TODO(devlin): We should also prompt for re-enable for other reasons,
368 // like user-disabled.
369 // For clarity, explicitly end any prior reenable process.
370 extension_reenabler_
.reset();
371 extension_reenabler_
= ExtensionReenabler::PromptForReenable(
372 registry
->disabled_extensions().GetByID(webstore_item_id
),
376 base::Bind(&TabHelper::OnReenableComplete
,
377 weak_ptr_factory_
.GetWeakPtr(),
381 // TODO(devlin): We should adddress the case of the extension already
382 // being installed and enabled.
383 WebstoreAPI::Get(profile
)->OnInlineInstallStart(
384 return_route_id
, this, webstore_item_id
, listeners_mask
);
386 WebstoreStandaloneInstaller::Callback callback
=
387 base::Bind(&TabHelper::OnInlineInstallComplete
,
388 base::Unretained(this),
391 scoped_refptr
<WebstoreInlineInstaller
> installer(
392 webstore_inline_installer_factory_
->CreateInstaller(
397 installer
->BeginInstall();
401 void TabHelper::OnGetAppInstallState(const GURL
& requestor_url
,
404 ExtensionRegistry
* registry
=
405 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
406 const ExtensionSet
& extensions
= registry
->enabled_extensions();
407 const ExtensionSet
& disabled_extensions
= registry
->disabled_extensions();
410 if (extensions
.GetHostedAppByURL(requestor_url
))
411 state
= extension_misc::kAppStateInstalled
;
412 else if (disabled_extensions
.GetHostedAppByURL(requestor_url
))
413 state
= extension_misc::kAppStateDisabled
;
415 state
= extension_misc::kAppStateNotInstalled
;
417 Send(new ExtensionMsg_GetAppInstallStateResponse(
418 return_route_id
, state
, callback_id
));
421 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params
& request
) {
422 extension_function_dispatcher_
.Dispatch(request
,
423 web_contents()->GetRenderViewHost());
426 void TabHelper::OnContentScriptsExecuting(
427 const ScriptExecutionObserver::ExecutingScriptsMap
& executing_scripts_map
,
428 const GURL
& on_url
) {
430 ScriptExecutionObserver
,
431 script_execution_observers_
,
432 OnScriptsExecuted(web_contents(), executing_scripts_map
, on_url
));
435 void TabHelper::OnWatchedPageChange(
436 const std::vector
<std::string
>& css_selectors
) {
437 if (ExtensionSystem::Get(profile_
)->extension_service() &&
438 RulesRegistryService::Get(profile_
)) {
439 RulesRegistryService::Get(profile_
)->content_rules_registry()->Apply(
440 web_contents(), css_selectors
);
444 void TabHelper::OnDetailedConsoleMessageAdded(
445 const base::string16
& message
,
446 const base::string16
& source
,
447 const StackTrace
& stack_trace
,
448 int32 severity_level
) {
449 if (IsSourceFromAnExtension(source
)) {
450 content::RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
451 ErrorConsole::Get(profile_
)->ReportError(
452 scoped_ptr
<ExtensionError
>(new RuntimeError(
453 extension_app_
? extension_app_
->id() : std::string(),
454 profile_
->IsOffTheRecord(),
459 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
460 static_cast<logging::LogSeverity
>(severity_level
),
462 rvh
->GetProcess()->GetID())));
466 const Extension
* TabHelper::GetExtension(const std::string
& extension_app_id
) {
467 if (extension_app_id
.empty())
470 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
471 return ExtensionRegistry::Get(context
)->enabled_extensions().GetByID(
475 void TabHelper::UpdateExtensionAppIcon(const Extension
* extension
) {
476 extension_app_icon_
.reset();
477 // Ensure previously enqueued callbacks are ignored.
478 image_loader_ptr_factory_
.InvalidateWeakPtrs();
480 // Enqueue OnImageLoaded callback.
483 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
484 ImageLoader
* loader
= ImageLoader::Get(profile
);
485 loader
->LoadImageAsync(
487 IconsInfo::GetIconResource(extension
,
488 extension_misc::EXTENSION_ICON_SMALL
,
489 ExtensionIconSet::MATCH_BIGGER
),
490 gfx::Size(extension_misc::EXTENSION_ICON_SMALL
,
491 extension_misc::EXTENSION_ICON_SMALL
),
492 base::Bind(&TabHelper::OnImageLoaded
,
493 image_loader_ptr_factory_
.GetWeakPtr()));
497 void TabHelper::SetAppIcon(const SkBitmap
& app_icon
) {
498 extension_app_icon_
= app_icon
;
499 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE
);
502 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
503 WebstoreInlineInstallerFactory
* factory
) {
504 webstore_inline_installer_factory_
.reset(factory
);
507 void TabHelper::OnImageLoaded(const gfx::Image
& image
) {
508 if (!image
.IsEmpty()) {
509 extension_app_icon_
= *image
.ToSkBitmap();
510 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB
);
514 WindowController
* TabHelper::GetExtensionWindowController() const {
515 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
518 void TabHelper::OnReenableComplete(int install_id
,
520 ExtensionReenabler::ReenableResult result
) {
521 extension_reenabler_
.reset();
522 // Map the re-enable results to webstore-install results.
523 webstore_install::Result webstore_result
= webstore_install::SUCCESS
;
526 case ExtensionReenabler::REENABLE_SUCCESS
:
527 break; // already set
528 case ExtensionReenabler::USER_CANCELED
:
529 webstore_result
= webstore_install::USER_CANCELLED
;
530 error
= "User canceled install.";
532 case ExtensionReenabler::NOT_ALLOWED
:
533 webstore_result
= webstore_install::NOT_PERMITTED
;
534 error
= "Install not permitted.";
536 case ExtensionReenabler::ABORTED
:
537 webstore_result
= webstore_install::ABORTED
;
538 error
= "Aborted due to tab closing.";
542 OnInlineInstallComplete(install_id
,
544 result
== ExtensionReenabler::REENABLE_SUCCESS
,
549 void TabHelper::OnInlineInstallComplete(int install_id
,
552 const std::string
& error
,
553 webstore_install::Result result
) {
554 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
558 success
? std::string() : error
,
562 WebContents
* TabHelper::GetAssociatedWebContents() const {
563 return web_contents();
566 void TabHelper::GetApplicationInfo(WebAppAction action
) {
567 NavigationEntry
* entry
=
568 web_contents()->GetController().GetLastCommittedEntry();
572 pending_web_app_action_
= action
;
573 last_committed_nav_entry_unique_id_
= entry
->GetUniqueID();
575 Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
578 void TabHelper::Observe(int type
,
579 const content::NotificationSource
& source
,
580 const content::NotificationDetails
& details
) {
581 DCHECK_EQ(content::NOTIFICATION_LOAD_STOP
, type
);
582 const NavigationController
& controller
=
583 *content::Source
<NavigationController
>(source
).ptr();
584 DCHECK_EQ(controller
.GetWebContents(), web_contents());
586 if (update_shortcut_on_load_complete_
) {
587 update_shortcut_on_load_complete_
= false;
588 // Schedule a shortcut update when web application info is available if
589 // last committed entry is not NULL. Last committed entry could be NULL
590 // when an interstitial page is injected (e.g. bad https certificate,
591 // malware site etc). When this happens, we abort the shortcut update.
592 if (controller
.GetLastCommittedEntry())
593 GetApplicationInfo(UPDATE_SHORTCUT
);
597 void TabHelper::SetTabId(RenderViewHost
* render_view_host
) {
598 render_view_host
->Send(
599 new ExtensionMsg_SetTabId(render_view_host
->GetRoutingID(),
600 SessionTabHelper::IdForTab(web_contents())));
603 } // namespace extensions