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/chrome_extension_web_contents_observer.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_frame_host.h"
44 #include "content/public/browser/render_process_host.h"
45 #include "content/public/browser/render_view_host.h"
46 #include "content/public/browser/web_contents.h"
47 #include "content/public/common/frame_navigate_params.h"
48 #include "extensions/browser/api/declarative/rules_registry_service.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/extension_web_contents_observer.h"
53 #include "extensions/browser/image_loader.h"
54 #include "extensions/common/constants.h"
55 #include "extensions/common/extension.h"
56 #include "extensions/common/extension_icon_set.h"
57 #include "extensions/common/extension_messages.h"
58 #include "extensions/common/extension_resource.h"
59 #include "extensions/common/extension_urls.h"
60 #include "extensions/common/feature_switch.h"
61 #include "extensions/common/manifest_handlers/icons_handler.h"
64 #include "chrome/browser/web_applications/web_app_win.h"
67 using content::NavigationController
;
68 using content::NavigationEntry
;
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
),
77 profile_(Profile::FromBrowserContext(web_contents
->GetBrowserContext())),
79 pending_web_app_action_(NONE
),
80 last_committed_nav_entry_unique_id_(0),
81 update_shortcut_on_load_complete_(false),
83 new ScriptExecutor(web_contents
, &script_execution_observers_
)),
84 location_bar_controller_(new LocationBarController(web_contents
)),
85 active_script_controller_(new ActiveScriptController(web_contents
)),
86 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()),
87 image_loader_ptr_factory_(this),
88 weak_ptr_factory_(this) {
89 // The ActiveTabPermissionManager requires a session ID; ensure this
90 // WebContents has one.
91 SessionTabHelper::CreateForWebContents(web_contents
);
92 // The Unretained() is safe because ForEachFrame is synchronous.
93 web_contents
->ForEachFrame(
94 base::Bind(&TabHelper::SetTabId
, base::Unretained(this)));
95 active_tab_permission_granter_
.reset(new ActiveTabPermissionGranter(
97 SessionTabHelper::IdForTab(web_contents
),
100 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_
));
102 InvokeForContentRulesRegistries([this](ContentRulesRegistry
* registry
) {
103 registry
->MonitorWebContentsForRuleEvaluation(this->web_contents());
106 // We need an ExtensionWebContentsObserver, so make sure one exists (this is
107 // a no-op if one already does).
108 ChromeExtensionWebContentsObserver::CreateForWebContents(web_contents
);
109 ExtensionWebContentsObserver::GetForWebContents(web_contents
)->dispatcher()->
113 content::NOTIFICATION_LOAD_STOP
,
114 content::Source
<NavigationController
>(
115 &web_contents
->GetController()));
118 TabHelper::~TabHelper() {
119 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_
));
122 void TabHelper::CreateApplicationShortcuts() {
123 DCHECK(CanCreateApplicationShortcuts());
124 if (pending_web_app_action_
!= NONE
)
127 // Start fetching web app info for CreateApplicationShortcut dialog and show
128 // the dialog when the data is available in OnDidGetApplicationInfo.
129 GetApplicationInfo(CREATE_SHORTCUT
);
132 void TabHelper::CreateHostedAppFromWebContents() {
133 DCHECK(CanCreateBookmarkApp());
134 if (pending_web_app_action_
!= NONE
)
137 // Start fetching web app info for CreateApplicationShortcut dialog and show
138 // the dialog when the data is available in OnDidGetApplicationInfo.
139 GetApplicationInfo(CREATE_HOSTED_APP
);
142 bool TabHelper::CanCreateApplicationShortcuts() const {
143 #if defined(OS_MACOSX)
146 return web_app::IsValidUrl(web_contents()->GetURL());
150 bool TabHelper::CanCreateBookmarkApp() const {
151 return !profile_
->IsGuestSession() &&
152 !profile_
->IsSystemProfile() &&
153 IsValidBookmarkAppUrl(web_contents()->GetURL());
156 void TabHelper::AddScriptExecutionObserver(ScriptExecutionObserver
* observer
) {
157 script_execution_observers_
.AddObserver(observer
);
160 void TabHelper::RemoveScriptExecutionObserver(
161 ScriptExecutionObserver
* observer
) {
162 script_execution_observers_
.RemoveObserver(observer
);
165 void TabHelper::SetExtensionApp(const Extension
* extension
) {
166 DCHECK(!extension
|| AppLaunchInfo::GetFullLaunchURL(extension
).is_valid());
167 if (extension_app_
== extension
)
170 extension_app_
= extension
;
172 UpdateExtensionAppIcon(extension_app_
);
174 content::NotificationService::current()->Notify(
175 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
,
176 content::Source
<TabHelper
>(this),
177 content::NotificationService::NoDetails());
180 void TabHelper::SetExtensionAppById(const std::string
& extension_app_id
) {
181 const Extension
* extension
= GetExtension(extension_app_id
);
183 SetExtensionApp(extension
);
186 void TabHelper::SetExtensionAppIconById(const std::string
& extension_app_id
) {
187 const Extension
* extension
= GetExtension(extension_app_id
);
189 UpdateExtensionAppIcon(extension
);
192 SkBitmap
* TabHelper::GetExtensionAppIcon() {
193 if (extension_app_icon_
.empty())
196 return &extension_app_icon_
;
199 // Encapsulates the logic to decide which ContentRulesRegistries need to be
200 // invoked, depending on whether this WebContents is associated with an Original
201 // or OffTheRecord profile. In the latter case, we need to invoke on both the
202 // Original and OffTheRecord ContentRulesRegistries since the Original registry
203 // handles spanning-mode incognito extensions.
204 template <class Func
>
205 void TabHelper::InvokeForContentRulesRegistries(const Func
& func
) {
206 RulesRegistryService
* rules_registry_service
=
207 RulesRegistryService::Get(profile_
);
208 if (rules_registry_service
) {
209 func(rules_registry_service
->content_rules_registry());
210 if (profile_
->IsOffTheRecord()) {
211 // The original profile's content rules registry handles rules for
212 // spanning extensions in incognito profiles, so invoke it also.
213 RulesRegistryService
* original_profile_rules_registry_service
=
214 RulesRegistryService::Get(profile_
->GetOriginalProfile());
215 DCHECK_NE(rules_registry_service
,
216 original_profile_rules_registry_service
);
217 if (original_profile_rules_registry_service
)
218 func(original_profile_rules_registry_service
->content_rules_registry());
223 void TabHelper::FinishCreateBookmarkApp(
224 const Extension
* extension
,
225 const WebApplicationInfo
& web_app_info
) {
226 pending_web_app_action_
= NONE
;
229 void TabHelper::RenderFrameCreated(content::RenderFrameHost
* host
) {
233 void TabHelper::DidNavigateMainFrame(
234 const content::LoadCommittedDetails
& details
,
235 const content::FrameNavigateParams
& params
) {
236 InvokeForContentRulesRegistries(
237 [this, &details
, ¶ms
](ContentRulesRegistry
* registry
) {
238 registry
->DidNavigateMainFrame(web_contents(), details
, params
);
241 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
242 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context
);
243 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
245 if (util::IsNewBookmarkAppsEnabled()) {
246 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
247 if (browser
&& browser
->is_app()) {
248 const Extension
* extension
= registry
->GetExtensionById(
249 web_app::GetExtensionIdFromApplicationName(browser
->app_name()),
250 ExtensionRegistry::EVERYTHING
);
251 if (extension
&& AppLaunchInfo::GetFullLaunchURL(extension
).is_valid())
252 SetExtensionApp(extension
);
254 UpdateExtensionAppIcon(
255 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
258 UpdateExtensionAppIcon(
259 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
262 if (!details
.is_in_page
)
263 ExtensionActionAPI::Get(context
)->ClearAllValuesForTab(web_contents());
266 bool TabHelper::OnMessageReceived(const IPC::Message
& message
) {
268 IPC_BEGIN_MESSAGE_MAP(TabHelper
, message
)
269 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo
,
270 OnDidGetWebApplicationInfo
)
271 IPC_MESSAGE_UNHANDLED(handled
= false)
272 IPC_END_MESSAGE_MAP()
276 bool TabHelper::OnMessageReceived(const IPC::Message
& message
,
277 content::RenderFrameHost
* render_frame_host
) {
279 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(TabHelper
, message
, render_frame_host
)
280 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall
,
281 OnInlineWebstoreInstall
)
282 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState
,
283 OnGetAppInstallState
);
284 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting
,
285 OnContentScriptsExecuting
)
286 IPC_MESSAGE_UNHANDLED(handled
= false)
287 IPC_END_MESSAGE_MAP()
291 void TabHelper::DidCloneToNewWebContents(WebContents
* old_web_contents
,
292 WebContents
* new_web_contents
) {
293 // When the WebContents that this is attached to is cloned, give the new clone
294 // a TabHelper and copy state over.
295 CreateForWebContents(new_web_contents
);
296 TabHelper
* new_helper
= FromWebContents(new_web_contents
);
298 new_helper
->SetExtensionApp(extension_app());
299 new_helper
->extension_app_icon_
= extension_app_icon_
;
302 void TabHelper::OnDidGetWebApplicationInfo(const WebApplicationInfo
& info
) {
303 web_app_info_
= info
;
305 NavigationEntry
* entry
=
306 web_contents()->GetController().GetLastCommittedEntry();
307 if (!entry
|| last_committed_nav_entry_unique_id_
!= entry
->GetUniqueID())
309 last_committed_nav_entry_unique_id_
= 0;
311 switch (pending_web_app_action_
) {
312 #if !defined(OS_MACOSX)
313 case CREATE_SHORTCUT
: {
314 chrome::ShowCreateWebAppShortcutsDialog(
315 web_contents()->GetTopLevelNativeWindow(),
320 case CREATE_HOSTED_APP
: {
321 if (web_app_info_
.app_url
.is_empty())
322 web_app_info_
.app_url
= web_contents()->GetURL();
324 if (web_app_info_
.title
.empty())
325 web_app_info_
.title
= web_contents()->GetTitle();
326 if (web_app_info_
.title
.empty())
327 web_app_info_
.title
= base::UTF8ToUTF16(web_app_info_
.app_url
.spec());
329 bookmark_app_helper_
.reset(
330 new BookmarkAppHelper(profile_
, web_app_info_
, web_contents()));
331 bookmark_app_helper_
->Create(base::Bind(
332 &TabHelper::FinishCreateBookmarkApp
, weak_ptr_factory_
.GetWeakPtr()));
335 case UPDATE_SHORTCUT
: {
336 web_app::UpdateShortcutForTabContents(web_contents());
344 // The hosted app action will be cleared once the installation completes or
346 if (pending_web_app_action_
!= CREATE_HOSTED_APP
)
347 pending_web_app_action_
= NONE
;
350 void TabHelper::OnInlineWebstoreInstall(content::RenderFrameHost
* host
,
353 const std::string
& webstore_item_id
,
354 const GURL
& requestor_url
,
355 int listeners_mask
) {
356 // Check that the listener is reasonable. We should never get anything other
357 // than an install stage listener, a download listener, or both.
358 if ((listeners_mask
& ~(api::webstore::INSTALL_STAGE_LISTENER
|
359 api::webstore::DOWNLOAD_PROGRESS_LISTENER
)) != 0 ||
360 requestor_url
.is_empty()) {
364 // Inform the Webstore API that an inline install is happening, in case the
365 // page requested status updates.
366 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
367 if (registry
->disabled_extensions().Contains(webstore_item_id
) &&
368 (ExtensionPrefs::Get(profile_
)->GetDisableReasons(webstore_item_id
) &
369 Extension::DISABLE_PERMISSIONS_INCREASE
) != 0) {
370 // The extension was disabled due to permissions increase. Prompt for
372 // TODO(devlin): We should also prompt for re-enable for other reasons,
373 // like user-disabled.
374 // For clarity, explicitly end any prior reenable process.
375 extension_reenabler_
.reset();
376 extension_reenabler_
= ExtensionReenabler::PromptForReenable(
377 registry
->disabled_extensions().GetByID(webstore_item_id
),
381 base::Bind(&TabHelper::OnReenableComplete
,
382 weak_ptr_factory_
.GetWeakPtr(),
386 // TODO(devlin): We should adddress the case of the extension already
387 // being installed and enabled.
388 WebstoreAPI::Get(profile_
)->OnInlineInstallStart(
389 return_route_id
, this, webstore_item_id
, listeners_mask
);
391 WebstoreStandaloneInstaller::Callback callback
=
392 base::Bind(&TabHelper::OnInlineInstallComplete
,
393 base::Unretained(this),
396 scoped_refptr
<WebstoreInlineInstaller
> installer(
397 webstore_inline_installer_factory_
->CreateInstaller(
402 installer
->BeginInstall();
406 void TabHelper::OnGetAppInstallState(content::RenderFrameHost
* host
,
407 const GURL
& requestor_url
,
410 ExtensionRegistry
* registry
=
411 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
412 const ExtensionSet
& extensions
= registry
->enabled_extensions();
413 const ExtensionSet
& disabled_extensions
= registry
->disabled_extensions();
416 if (extensions
.GetHostedAppByURL(requestor_url
))
417 state
= extension_misc::kAppStateInstalled
;
418 else if (disabled_extensions
.GetHostedAppByURL(requestor_url
))
419 state
= extension_misc::kAppStateDisabled
;
421 state
= extension_misc::kAppStateNotInstalled
;
423 // We use the |host| to send the message because using
424 // WebContentsObserver::Send() defaults to using the main RenderView, which
425 // might be in a different process if the request came from a frame.
426 host
->Send(new ExtensionMsg_GetAppInstallStateResponse(return_route_id
, state
,
430 void TabHelper::OnContentScriptsExecuting(
431 content::RenderFrameHost
* host
,
432 const ScriptExecutionObserver::ExecutingScriptsMap
& executing_scripts_map
,
433 const GURL
& on_url
) {
435 ScriptExecutionObserver
,
436 script_execution_observers_
,
437 OnScriptsExecuted(web_contents(), executing_scripts_map
, on_url
));
440 const Extension
* TabHelper::GetExtension(const std::string
& extension_app_id
) {
441 if (extension_app_id
.empty())
444 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
445 return ExtensionRegistry::Get(context
)->enabled_extensions().GetByID(
449 void TabHelper::UpdateExtensionAppIcon(const Extension
* extension
) {
450 extension_app_icon_
.reset();
451 // Ensure previously enqueued callbacks are ignored.
452 image_loader_ptr_factory_
.InvalidateWeakPtrs();
454 // Enqueue OnImageLoaded callback.
456 ImageLoader
* loader
= ImageLoader::Get(profile_
);
457 loader
->LoadImageAsync(
459 IconsInfo::GetIconResource(extension
,
460 extension_misc::EXTENSION_ICON_SMALL
,
461 ExtensionIconSet::MATCH_BIGGER
),
462 gfx::Size(extension_misc::EXTENSION_ICON_SMALL
,
463 extension_misc::EXTENSION_ICON_SMALL
),
464 base::Bind(&TabHelper::OnImageLoaded
,
465 image_loader_ptr_factory_
.GetWeakPtr()));
469 void TabHelper::SetAppIcon(const SkBitmap
& app_icon
) {
470 extension_app_icon_
= app_icon
;
471 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE
);
474 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
475 WebstoreInlineInstallerFactory
* factory
) {
476 webstore_inline_installer_factory_
.reset(factory
);
479 void TabHelper::OnImageLoaded(const gfx::Image
& image
) {
480 if (!image
.IsEmpty()) {
481 extension_app_icon_
= *image
.ToSkBitmap();
482 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB
);
486 WindowController
* TabHelper::GetExtensionWindowController() const {
487 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
490 void TabHelper::OnReenableComplete(int install_id
,
492 ExtensionReenabler::ReenableResult result
) {
493 extension_reenabler_
.reset();
494 // Map the re-enable results to webstore-install results.
495 webstore_install::Result webstore_result
= webstore_install::SUCCESS
;
498 case ExtensionReenabler::REENABLE_SUCCESS
:
499 break; // already set
500 case ExtensionReenabler::USER_CANCELED
:
501 webstore_result
= webstore_install::USER_CANCELLED
;
502 error
= "User canceled install.";
504 case ExtensionReenabler::NOT_ALLOWED
:
505 webstore_result
= webstore_install::NOT_PERMITTED
;
506 error
= "Install not permitted.";
508 case ExtensionReenabler::ABORTED
:
509 webstore_result
= webstore_install::ABORTED
;
510 error
= "Aborted due to tab closing.";
514 OnInlineInstallComplete(install_id
,
516 result
== ExtensionReenabler::REENABLE_SUCCESS
,
521 void TabHelper::OnInlineInstallComplete(int install_id
,
524 const std::string
& error
,
525 webstore_install::Result result
) {
526 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
530 success
? std::string() : error
,
534 WebContents
* TabHelper::GetAssociatedWebContents() const {
535 return web_contents();
538 void TabHelper::GetApplicationInfo(WebAppAction action
) {
539 NavigationEntry
* entry
=
540 web_contents()->GetController().GetLastCommittedEntry();
544 pending_web_app_action_
= action
;
545 last_committed_nav_entry_unique_id_
= entry
->GetUniqueID();
547 Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
550 void TabHelper::Observe(int type
,
551 const content::NotificationSource
& source
,
552 const content::NotificationDetails
& details
) {
553 DCHECK_EQ(content::NOTIFICATION_LOAD_STOP
, type
);
554 const NavigationController
& controller
=
555 *content::Source
<NavigationController
>(source
).ptr();
556 DCHECK_EQ(controller
.GetWebContents(), web_contents());
558 if (update_shortcut_on_load_complete_
) {
559 update_shortcut_on_load_complete_
= false;
560 // Schedule a shortcut update when web application info is available if
561 // last committed entry is not NULL. Last committed entry could be NULL
562 // when an interstitial page is injected (e.g. bad https certificate,
563 // malware site etc). When this happens, we abort the shortcut update.
564 if (controller
.GetLastCommittedEntry())
565 GetApplicationInfo(UPDATE_SHORTCUT
);
569 void TabHelper::SetTabId(content::RenderFrameHost
* render_frame_host
) {
570 render_frame_host
->Send(
571 new ExtensionMsg_SetTabId(render_frame_host
->GetRoutingID(),
572 SessionTabHelper::IdForTab(web_contents())));
575 } // namespace extensions