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 IsValidBookmarkAppUrl(web_contents()->GetURL());
149 void TabHelper::AddScriptExecutionObserver(ScriptExecutionObserver
* observer
) {
150 script_execution_observers_
.AddObserver(observer
);
153 void TabHelper::RemoveScriptExecutionObserver(
154 ScriptExecutionObserver
* observer
) {
155 script_execution_observers_
.RemoveObserver(observer
);
158 void TabHelper::SetExtensionApp(const Extension
* extension
) {
159 DCHECK(!extension
|| AppLaunchInfo::GetFullLaunchURL(extension
).is_valid());
160 if (extension_app_
== extension
)
163 extension_app_
= extension
;
165 UpdateExtensionAppIcon(extension_app_
);
167 content::NotificationService::current()->Notify(
168 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED
,
169 content::Source
<TabHelper
>(this),
170 content::NotificationService::NoDetails());
173 void TabHelper::SetExtensionAppById(const std::string
& extension_app_id
) {
174 const Extension
* extension
= GetExtension(extension_app_id
);
176 SetExtensionApp(extension
);
179 void TabHelper::SetExtensionAppIconById(const std::string
& extension_app_id
) {
180 const Extension
* extension
= GetExtension(extension_app_id
);
182 UpdateExtensionAppIcon(extension
);
185 SkBitmap
* TabHelper::GetExtensionAppIcon() {
186 if (extension_app_icon_
.empty())
189 return &extension_app_icon_
;
192 void TabHelper::FinishCreateBookmarkApp(
193 const Extension
* extension
,
194 const WebApplicationInfo
& web_app_info
) {
195 pending_web_app_action_
= NONE
;
198 void TabHelper::RenderViewCreated(RenderViewHost
* render_view_host
) {
199 SetTabId(render_view_host
);
202 void TabHelper::DidNavigateMainFrame(
203 const content::LoadCommittedDetails
& details
,
204 const content::FrameNavigateParams
& params
) {
205 if (ExtensionSystem::Get(profile_
)->extension_service() &&
206 RulesRegistryService::Get(profile_
)) {
207 RulesRegistryService::Get(profile_
)->content_rules_registry()->
208 DidNavigateMainFrame(web_contents(), details
, params
);
211 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
212 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context
);
213 const ExtensionSet
& enabled_extensions
= registry
->enabled_extensions();
215 if (util::IsNewBookmarkAppsEnabled()) {
216 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
217 if (browser
&& browser
->is_app()) {
218 const Extension
* extension
= registry
->GetExtensionById(
219 web_app::GetExtensionIdFromApplicationName(browser
->app_name()),
220 ExtensionRegistry::EVERYTHING
);
221 if (extension
&& AppLaunchInfo::GetFullLaunchURL(extension
).is_valid())
222 SetExtensionApp(extension
);
224 UpdateExtensionAppIcon(
225 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
228 UpdateExtensionAppIcon(
229 enabled_extensions
.GetExtensionOrAppByURL(params
.url
));
232 if (!details
.is_in_page
)
233 ExtensionActionAPI::Get(context
)->ClearAllValuesForTab(web_contents());
236 bool TabHelper::OnMessageReceived(const IPC::Message
& message
) {
238 IPC_BEGIN_MESSAGE_MAP(TabHelper
, message
)
239 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo
,
240 OnDidGetWebApplicationInfo
)
241 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall
,
242 OnInlineWebstoreInstall
)
243 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState
,
244 OnGetAppInstallState
);
245 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request
, OnRequest
)
246 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting
,
247 OnContentScriptsExecuting
)
248 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange
,
250 IPC_MESSAGE_UNHANDLED(handled
= false)
251 IPC_END_MESSAGE_MAP()
255 bool TabHelper::OnMessageReceived(const IPC::Message
& message
,
256 content::RenderFrameHost
* render_frame_host
) {
258 IPC_BEGIN_MESSAGE_MAP(TabHelper
, message
)
259 IPC_MESSAGE_HANDLER(ExtensionHostMsg_DetailedConsoleMessageAdded
,
260 OnDetailedConsoleMessageAdded
)
261 IPC_MESSAGE_UNHANDLED(handled
= false)
262 IPC_END_MESSAGE_MAP()
266 void TabHelper::DidCloneToNewWebContents(WebContents
* old_web_contents
,
267 WebContents
* new_web_contents
) {
268 // When the WebContents that this is attached to is cloned, give the new clone
269 // a TabHelper and copy state over.
270 CreateForWebContents(new_web_contents
);
271 TabHelper
* new_helper
= FromWebContents(new_web_contents
);
273 new_helper
->SetExtensionApp(extension_app());
274 new_helper
->extension_app_icon_
= extension_app_icon_
;
277 void TabHelper::OnDidGetWebApplicationInfo(const WebApplicationInfo
& info
) {
278 web_app_info_
= info
;
280 NavigationEntry
* entry
=
281 web_contents()->GetController().GetLastCommittedEntry();
282 if (!entry
|| last_committed_nav_entry_unique_id_
!= entry
->GetUniqueID())
284 last_committed_nav_entry_unique_id_
= 0;
286 switch (pending_web_app_action_
) {
287 #if !defined(OS_MACOSX)
288 case CREATE_SHORTCUT
: {
289 chrome::ShowCreateWebAppShortcutsDialog(
290 web_contents()->GetTopLevelNativeWindow(),
295 case CREATE_HOSTED_APP
: {
296 if (web_app_info_
.app_url
.is_empty())
297 web_app_info_
.app_url
= web_contents()->GetURL();
299 if (web_app_info_
.title
.empty())
300 web_app_info_
.title
= web_contents()->GetTitle();
301 if (web_app_info_
.title
.empty())
302 web_app_info_
.title
= base::UTF8ToUTF16(web_app_info_
.app_url
.spec());
304 bookmark_app_helper_
.reset(
305 new BookmarkAppHelper(profile_
, web_app_info_
, web_contents()));
306 bookmark_app_helper_
->Create(base::Bind(
307 &TabHelper::FinishCreateBookmarkApp
, weak_ptr_factory_
.GetWeakPtr()));
310 case UPDATE_SHORTCUT
: {
311 web_app::UpdateShortcutForTabContents(web_contents());
319 // The hosted app action will be cleared once the installation completes or
321 if (pending_web_app_action_
!= CREATE_HOSTED_APP
)
322 pending_web_app_action_
= NONE
;
325 void TabHelper::OnInlineWebstoreInstall(int install_id
,
327 const std::string
& webstore_item_id
,
328 const GURL
& requestor_url
,
329 int listeners_mask
) {
330 // Check that the listener is reasonable. We should never get anything other
331 // than an install stage listener, a download listener, or both.
332 if ((listeners_mask
& ~(api::webstore::INSTALL_STAGE_LISTENER
|
333 api::webstore::DOWNLOAD_PROGRESS_LISTENER
)) != 0 ||
334 requestor_url
.is_empty()) {
338 // Inform the Webstore API that an inline install is happening, in case the
339 // page requested status updates.
341 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
343 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile
);
344 if (registry
->disabled_extensions().Contains(webstore_item_id
) &&
345 (ExtensionPrefs::Get(profile
)->GetDisableReasons(webstore_item_id
) &
346 Extension::DISABLE_PERMISSIONS_INCREASE
) != 0) {
347 // The extension was disabled due to permissions increase. Prompt for
349 // TODO(devlin): We should also prompt for re-enable for other reasons,
350 // like user-disabled.
351 // For clarity, explicitly end any prior reenable process.
352 extension_reenabler_
.reset();
353 extension_reenabler_
= ExtensionReenabler::PromptForReenable(
354 registry
->disabled_extensions().GetByID(webstore_item_id
),
358 base::Bind(&TabHelper::OnReenableComplete
,
359 weak_ptr_factory_
.GetWeakPtr(),
363 // TODO(devlin): We should adddress the case of the extension already
364 // being installed and enabled.
365 WebstoreAPI::Get(profile
)->OnInlineInstallStart(
366 return_route_id
, this, webstore_item_id
, listeners_mask
);
368 WebstoreStandaloneInstaller::Callback callback
=
369 base::Bind(&TabHelper::OnInlineInstallComplete
,
370 base::Unretained(this),
373 scoped_refptr
<WebstoreInlineInstaller
> installer(
374 webstore_inline_installer_factory_
->CreateInstaller(
379 installer
->BeginInstall();
383 void TabHelper::OnGetAppInstallState(const GURL
& requestor_url
,
386 ExtensionRegistry
* registry
=
387 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
388 const ExtensionSet
& extensions
= registry
->enabled_extensions();
389 const ExtensionSet
& disabled_extensions
= registry
->disabled_extensions();
392 if (extensions
.GetHostedAppByURL(requestor_url
))
393 state
= extension_misc::kAppStateInstalled
;
394 else if (disabled_extensions
.GetHostedAppByURL(requestor_url
))
395 state
= extension_misc::kAppStateDisabled
;
397 state
= extension_misc::kAppStateNotInstalled
;
399 Send(new ExtensionMsg_GetAppInstallStateResponse(
400 return_route_id
, state
, callback_id
));
403 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params
& request
) {
404 extension_function_dispatcher_
.Dispatch(request
,
405 web_contents()->GetRenderViewHost());
408 void TabHelper::OnContentScriptsExecuting(
409 const ScriptExecutionObserver::ExecutingScriptsMap
& executing_scripts_map
,
410 const GURL
& on_url
) {
412 ScriptExecutionObserver
,
413 script_execution_observers_
,
414 OnScriptsExecuted(web_contents(), executing_scripts_map
, on_url
));
417 void TabHelper::OnWatchedPageChange(
418 const std::vector
<std::string
>& css_selectors
) {
419 if (ExtensionSystem::Get(profile_
)->extension_service() &&
420 RulesRegistryService::Get(profile_
)) {
421 RulesRegistryService::Get(profile_
)->content_rules_registry()->Apply(
422 web_contents(), css_selectors
);
426 void TabHelper::OnDetailedConsoleMessageAdded(
427 const base::string16
& message
,
428 const base::string16
& source
,
429 const StackTrace
& stack_trace
,
430 int32 severity_level
) {
431 if (IsSourceFromAnExtension(source
)) {
432 content::RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
433 ErrorConsole::Get(profile_
)->ReportError(
434 scoped_ptr
<ExtensionError
>(new RuntimeError(
435 extension_app_
? extension_app_
->id() : std::string(),
436 profile_
->IsOffTheRecord(),
441 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
442 static_cast<logging::LogSeverity
>(severity_level
),
444 rvh
->GetProcess()->GetID())));
448 const Extension
* TabHelper::GetExtension(const std::string
& extension_app_id
) {
449 if (extension_app_id
.empty())
452 content::BrowserContext
* context
= web_contents()->GetBrowserContext();
453 return ExtensionRegistry::Get(context
)->enabled_extensions().GetByID(
457 void TabHelper::UpdateExtensionAppIcon(const Extension
* extension
) {
458 extension_app_icon_
.reset();
459 // Ensure previously enqueued callbacks are ignored.
460 image_loader_ptr_factory_
.InvalidateWeakPtrs();
462 // Enqueue OnImageLoaded callback.
465 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
466 ImageLoader
* loader
= ImageLoader::Get(profile
);
467 loader
->LoadImageAsync(
469 IconsInfo::GetIconResource(extension
,
470 extension_misc::EXTENSION_ICON_SMALL
,
471 ExtensionIconSet::MATCH_BIGGER
),
472 gfx::Size(extension_misc::EXTENSION_ICON_SMALL
,
473 extension_misc::EXTENSION_ICON_SMALL
),
474 base::Bind(&TabHelper::OnImageLoaded
,
475 image_loader_ptr_factory_
.GetWeakPtr()));
479 void TabHelper::SetAppIcon(const SkBitmap
& app_icon
) {
480 extension_app_icon_
= app_icon
;
481 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE
);
484 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
485 WebstoreInlineInstallerFactory
* factory
) {
486 webstore_inline_installer_factory_
.reset(factory
);
489 void TabHelper::OnImageLoaded(const gfx::Image
& image
) {
490 if (!image
.IsEmpty()) {
491 extension_app_icon_
= *image
.ToSkBitmap();
492 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB
);
496 WindowController
* TabHelper::GetExtensionWindowController() const {
497 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
500 void TabHelper::OnReenableComplete(int install_id
,
502 ExtensionReenabler::ReenableResult result
) {
503 extension_reenabler_
.reset();
504 // Map the re-enable results to webstore-install results.
505 webstore_install::Result webstore_result
= webstore_install::SUCCESS
;
508 case ExtensionReenabler::REENABLE_SUCCESS
:
509 break; // already set
510 case ExtensionReenabler::USER_CANCELED
:
511 webstore_result
= webstore_install::USER_CANCELLED
;
512 error
= "User canceled install.";
514 case ExtensionReenabler::NOT_ALLOWED
:
515 webstore_result
= webstore_install::NOT_PERMITTED
;
516 error
= "Install not permitted.";
518 case ExtensionReenabler::ABORTED
:
519 webstore_result
= webstore_install::ABORTED
;
520 error
= "Aborted due to tab closing.";
524 OnInlineInstallComplete(install_id
,
526 result
== ExtensionReenabler::REENABLE_SUCCESS
,
531 void TabHelper::OnInlineInstallComplete(int install_id
,
534 const std::string
& error
,
535 webstore_install::Result result
) {
536 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
540 success
? std::string() : error
,
544 WebContents
* TabHelper::GetAssociatedWebContents() const {
545 return web_contents();
548 void TabHelper::GetApplicationInfo(WebAppAction action
) {
549 NavigationEntry
* entry
=
550 web_contents()->GetController().GetLastCommittedEntry();
554 pending_web_app_action_
= action
;
555 last_committed_nav_entry_unique_id_
= entry
->GetUniqueID();
557 Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
560 void TabHelper::Observe(int type
,
561 const content::NotificationSource
& source
,
562 const content::NotificationDetails
& details
) {
563 DCHECK_EQ(content::NOTIFICATION_LOAD_STOP
, type
);
564 const NavigationController
& controller
=
565 *content::Source
<NavigationController
>(source
).ptr();
566 DCHECK_EQ(controller
.GetWebContents(), web_contents());
568 if (update_shortcut_on_load_complete_
) {
569 update_shortcut_on_load_complete_
= false;
570 // Schedule a shortcut update when web application info is available if
571 // last committed entry is not NULL. Last committed entry could be NULL
572 // when an interstitial page is injected (e.g. bad https certificate,
573 // malware site etc). When this happens, we abort the shortcut update.
574 if (controller
.GetLastCommittedEntry())
575 GetApplicationInfo(UPDATE_SHORTCUT
);
579 void TabHelper::SetTabId(RenderViewHost
* render_view_host
) {
580 render_view_host
->Send(
581 new ExtensionMsg_SetTabId(render_view_host
->GetRoutingID(),
582 SessionTabHelper::IdForTab(web_contents())));
585 } // namespace extensions