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/browser_window.h"
30 #include "chrome/browser/ui/host_desktop.h"
31 #include "chrome/browser/web_applications/web_app.h"
32 #include "chrome/common/extensions/chrome_extension_messages.h"
33 #include "chrome/common/extensions/extension_constants.h"
34 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
35 #include "chrome/common/render_messages.h"
36 #include "chrome/common/url_constants.h"
37 #include "content/public/browser/invalidate_type.h"
38 #include "content/public/browser/navigation_controller.h"
39 #include "content/public/browser/navigation_details.h"
40 #include "content/public/browser/navigation_entry.h"
41 #include "content/public/browser/notification_service.h"
42 #include "content/public/browser/notification_source.h"
43 #include "content/public/browser/notification_types.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_error.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"
62 #if defined(OS_CHROMEOS)
63 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
67 #include "chrome/browser/web_applications/web_app_win.h"
70 using content::NavigationController;
71 using content::NavigationEntry;
72 using content::RenderViewHost;
73 using content::WebContents;
75 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
77 namespace extensions {
79 TabHelper::TabHelper(content::WebContents* web_contents)
80 : content::WebContentsObserver(web_contents),
82 extension_function_dispatcher_(
83 Profile::FromBrowserContext(web_contents->GetBrowserContext()),
85 pending_web_app_action_(NONE),
86 last_committed_page_id_(-1),
87 update_shortcut_on_load_complete_(false),
89 new ScriptExecutor(web_contents, &script_execution_observers_)),
90 location_bar_controller_(new LocationBarController(web_contents)),
91 active_script_controller_(new ActiveScriptController(web_contents)),
92 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()),
93 image_loader_ptr_factory_(this) {
94 // The ActiveTabPermissionManager requires a session ID; ensure this
95 // WebContents has one.
96 SessionTabHelper::CreateForWebContents(web_contents);
97 if (web_contents->GetRenderViewHost())
98 SetTabId(web_contents->GetRenderViewHost());
99 active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
101 SessionTabHelper::IdForTab(web_contents),
102 Profile::FromBrowserContext(web_contents->GetBrowserContext())));
104 // If more classes need to listen to global content script activity, then
105 // a separate routing class with an observer interface should be written.
106 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
108 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
111 content::NOTIFICATION_LOAD_STOP,
112 content::Source<NavigationController>(
113 &web_contents->GetController()));
116 TabHelper::~TabHelper() {
117 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
120 void TabHelper::CreateApplicationShortcuts() {
121 DCHECK(CanCreateApplicationShortcuts());
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 // Start fetching web app info for CreateApplicationShortcut dialog and show
130 // the dialog when the data is available in OnDidGetApplicationInfo.
131 GetApplicationInfo(CREATE_HOSTED_APP);
134 bool TabHelper::CanCreateApplicationShortcuts() const {
135 #if defined(OS_MACOSX)
138 return web_app::IsValidUrl(web_contents()->GetURL()) &&
139 pending_web_app_action_ == NONE;
143 bool TabHelper::CanCreateBookmarkApp() const {
144 #if defined(OS_MACOSX)
147 return IsValidBookmarkAppUrl(web_contents()->GetURL()) &&
148 pending_web_app_action_ == NONE;
152 void TabHelper::AddScriptExecutionObserver(ScriptExecutionObserver* observer) {
153 script_execution_observers_.AddObserver(observer);
156 void TabHelper::RemoveScriptExecutionObserver(
157 ScriptExecutionObserver* observer) {
158 script_execution_observers_.RemoveObserver(observer);
161 void TabHelper::SetExtensionApp(const Extension* extension) {
162 DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
163 if (extension_app_ == extension)
166 extension_app_ = extension;
168 UpdateExtensionAppIcon(extension_app_);
170 content::NotificationService::current()->Notify(
171 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
172 content::Source<TabHelper>(this),
173 content::NotificationService::NoDetails());
176 void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
177 const Extension* extension = GetExtension(extension_app_id);
179 SetExtensionApp(extension);
182 void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
183 const Extension* extension = GetExtension(extension_app_id);
185 UpdateExtensionAppIcon(extension);
188 SkBitmap* TabHelper::GetExtensionAppIcon() {
189 if (extension_app_icon_.empty())
192 return &extension_app_icon_;
195 void TabHelper::FinishCreateBookmarkApp(
196 const Extension* extension,
197 const WebApplicationInfo& web_app_info) {
198 pending_web_app_action_ = NONE;
200 // There was an error with downloading the icons or installing the app.
204 #if defined(OS_CHROMEOS)
205 ChromeLauncherController::instance()->PinAppWithID(extension->id());
208 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
210 browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id());
214 void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
215 SetTabId(render_view_host);
218 void TabHelper::DidNavigateMainFrame(
219 const content::LoadCommittedDetails& details,
220 const content::FrameNavigateParams& params) {
221 if (ExtensionSystem::Get(profile_)->extension_service() &&
222 RulesRegistryService::Get(profile_)) {
223 RulesRegistryService::Get(profile_)->content_rules_registry()->
224 DidNavigateMainFrame(web_contents(), details, params);
227 content::BrowserContext* context = web_contents()->GetBrowserContext();
228 ExtensionRegistry* registry = ExtensionRegistry::Get(context);
229 const ExtensionSet& enabled_extensions = registry->enabled_extensions();
231 if (util::IsStreamlinedHostedAppsEnabled()) {
232 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
233 if (browser && browser->is_app()) {
234 SetExtensionApp(registry->GetExtensionById(
235 web_app::GetExtensionIdFromApplicationName(browser->app_name()),
236 ExtensionRegistry::EVERYTHING));
238 UpdateExtensionAppIcon(
239 enabled_extensions.GetExtensionOrAppByURL(params.url));
242 UpdateExtensionAppIcon(
243 enabled_extensions.GetExtensionOrAppByURL(params.url));
246 if (!details.is_in_page)
247 ExtensionActionAPI::Get(context)->ClearAllValuesForTab(web_contents());
250 bool TabHelper::OnMessageReceived(const IPC::Message& message) {
252 IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
253 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo,
254 OnDidGetWebApplicationInfo)
255 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
256 OnInlineWebstoreInstall)
257 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
258 OnGetAppInstallState);
259 IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
260 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
261 OnContentScriptsExecuting)
262 IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
264 IPC_MESSAGE_UNHANDLED(handled = false)
265 IPC_END_MESSAGE_MAP()
269 bool TabHelper::OnMessageReceived(const IPC::Message& message,
270 content::RenderFrameHost* render_frame_host) {
272 IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
273 IPC_MESSAGE_HANDLER(ExtensionHostMsg_DetailedConsoleMessageAdded,
274 OnDetailedConsoleMessageAdded)
275 IPC_MESSAGE_UNHANDLED(handled = false)
276 IPC_END_MESSAGE_MAP()
280 void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
281 WebContents* new_web_contents) {
282 // When the WebContents that this is attached to is cloned, give the new clone
283 // a TabHelper and copy state over.
284 CreateForWebContents(new_web_contents);
285 TabHelper* new_helper = FromWebContents(new_web_contents);
287 new_helper->SetExtensionApp(extension_app());
288 new_helper->extension_app_icon_ = extension_app_icon_;
291 void TabHelper::OnDidGetWebApplicationInfo(const WebApplicationInfo& info) {
292 #if !defined(OS_MACOSX)
293 web_app_info_ = info;
295 NavigationEntry* entry =
296 web_contents()->GetController().GetLastCommittedEntry();
297 if (!entry || last_committed_page_id_ != entry->GetPageID())
299 last_committed_page_id_ = -1;
301 switch (pending_web_app_action_) {
302 case CREATE_SHORTCUT: {
303 chrome::ShowCreateWebAppShortcutsDialog(
304 web_contents()->GetTopLevelNativeWindow(),
308 case CREATE_HOSTED_APP: {
309 if (web_app_info_.app_url.is_empty())
310 web_app_info_.app_url = web_contents()->GetURL();
312 if (web_app_info_.title.empty())
313 web_app_info_.title = web_contents()->GetTitle();
314 if (web_app_info_.title.empty())
315 web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());
317 bookmark_app_helper_.reset(new BookmarkAppHelper(
318 ExtensionSystem::Get(profile_)->extension_service(),
319 web_app_info_, web_contents()));
320 bookmark_app_helper_->Create(base::Bind(
321 &TabHelper::FinishCreateBookmarkApp, base::Unretained(this)));
324 case UPDATE_SHORTCUT: {
325 web_app::UpdateShortcutForTabContents(web_contents());
333 // The hosted app action will be cleared once the installation completes or
335 if (pending_web_app_action_ != CREATE_HOSTED_APP)
336 pending_web_app_action_ = NONE;
340 void TabHelper::OnInlineWebstoreInstall(int install_id,
342 const std::string& webstore_item_id,
343 const GURL& requestor_url,
344 int listeners_mask) {
345 // Check that the listener is reasonable. We should never get anything other
346 // than an install stage listener, a download listener, or both.
347 if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
348 api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) {
352 // Inform the Webstore API that an inline install is happening, in case the
353 // page requested status updates.
355 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
356 WebstoreAPI::Get(profile)->OnInlineInstallStart(
357 return_route_id, this, webstore_item_id, listeners_mask);
359 WebstoreStandaloneInstaller::Callback callback =
360 base::Bind(&TabHelper::OnInlineInstallComplete,
361 base::Unretained(this),
364 scoped_refptr<WebstoreInlineInstaller> installer(
365 webstore_inline_installer_factory_->CreateInstaller(
370 installer->BeginInstall();
373 void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
376 ExtensionRegistry* registry =
377 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
378 const ExtensionSet& extensions = registry->enabled_extensions();
379 const ExtensionSet& disabled_extensions = registry->disabled_extensions();
382 if (extensions.GetHostedAppByURL(requestor_url))
383 state = extension_misc::kAppStateInstalled;
384 else if (disabled_extensions.GetHostedAppByURL(requestor_url))
385 state = extension_misc::kAppStateDisabled;
387 state = extension_misc::kAppStateNotInstalled;
389 Send(new ExtensionMsg_GetAppInstallStateResponse(
390 return_route_id, state, callback_id));
393 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
394 extension_function_dispatcher_.Dispatch(request,
395 web_contents()->GetRenderViewHost());
398 void TabHelper::OnContentScriptsExecuting(
399 const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
400 const GURL& on_url) {
402 ScriptExecutionObserver,
403 script_execution_observers_,
404 OnScriptsExecuted(web_contents(), executing_scripts_map, on_url));
407 void TabHelper::OnWatchedPageChange(
408 const std::vector<std::string>& css_selectors) {
409 if (ExtensionSystem::Get(profile_)->extension_service() &&
410 RulesRegistryService::Get(profile_)) {
411 RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
412 web_contents(), css_selectors);
416 void TabHelper::OnDetailedConsoleMessageAdded(
417 const base::string16& message,
418 const base::string16& source,
419 const StackTrace& stack_trace,
420 int32 severity_level) {
421 if (IsSourceFromAnExtension(source)) {
422 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
423 ErrorConsole::Get(profile_)->ReportError(
424 scoped_ptr<ExtensionError>(new RuntimeError(
425 extension_app_ ? extension_app_->id() : std::string(),
426 profile_->IsOffTheRecord(),
431 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
432 static_cast<logging::LogSeverity>(severity_level),
434 rvh->GetProcess()->GetID())));
438 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
439 if (extension_app_id.empty())
442 content::BrowserContext* context = web_contents()->GetBrowserContext();
443 return ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
447 void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
448 extension_app_icon_.reset();
449 // Ensure previously enqueued callbacks are ignored.
450 image_loader_ptr_factory_.InvalidateWeakPtrs();
452 // Enqueue OnImageLoaded callback.
455 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
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::OnInlineInstallComplete(int install_id,
493 const std::string& error,
494 webstore_install::Result result) {
495 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
499 success ? std::string() : error,
503 WebContents* TabHelper::GetAssociatedWebContents() const {
504 return web_contents();
507 void TabHelper::GetApplicationInfo(WebAppAction action) {
508 NavigationEntry* entry =
509 web_contents()->GetController().GetLastCommittedEntry();
513 pending_web_app_action_ = action;
514 last_committed_page_id_ = entry->GetPageID();
516 Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
519 void TabHelper::Observe(int type,
520 const content::NotificationSource& source,
521 const content::NotificationDetails& details) {
522 DCHECK_EQ(content::NOTIFICATION_LOAD_STOP, type);
523 const NavigationController& controller =
524 *content::Source<NavigationController>(source).ptr();
525 DCHECK_EQ(controller.GetWebContents(), web_contents());
527 if (update_shortcut_on_load_complete_) {
528 update_shortcut_on_load_complete_ = false;
529 // Schedule a shortcut update when web application info is available if
530 // last committed entry is not NULL. Last committed entry could be NULL
531 // when an interstitial page is injected (e.g. bad https certificate,
532 // malware site etc). When this happens, we abort the shortcut update.
533 if (controller.GetLastCommittedEntry())
534 GetApplicationInfo(UPDATE_SHORTCUT);
538 void TabHelper::SetTabId(RenderViewHost* render_view_host) {
539 render_view_host->Send(
540 new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
541 SessionTabHelper::IdForTab(web_contents())));
544 } // namespace extensions