16eed180460110d309c2a45380ad0dca1b717e1a
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / tab_helper.cc
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.
4
5 #include "chrome/browser/extensions/tab_helper.h"
6
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/crx_installer.h"
18 #include "chrome/browser/extensions/error_console/error_console.h"
19 #include "chrome/browser/extensions/extension_action.h"
20 #include "chrome/browser/extensions/extension_action_manager.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_tab_util.h"
23 #include "chrome/browser/extensions/image_loader.h"
24 #include "chrome/browser/extensions/page_action_controller.h"
25 #include "chrome/browser/extensions/script_executor.h"
26 #include "chrome/browser/extensions/webstore_inline_installer.h"
27 #include "chrome/browser/extensions/webstore_inline_installer_factory.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/sessions/session_id.h"
30 #include "chrome/browser/sessions/session_tab_helper.h"
31 #include "chrome/browser/shell_integration.h"
32 #include "chrome/browser/ui/browser_commands.h"
33 #include "chrome/browser/ui/browser_dialogs.h"
34 #include "chrome/browser/ui/browser_finder.h"
35 #include "chrome/browser/ui/browser_window.h"
36 #include "chrome/browser/ui/host_desktop.h"
37 #include "chrome/browser/ui/web_applications/web_app_ui.h"
38 #include "chrome/browser/web_applications/web_app.h"
39 #include "chrome/common/chrome_switches.h"
40 #include "chrome/common/extensions/chrome_extension_messages.h"
41 #include "chrome/common/extensions/extension_constants.h"
42 #include "chrome/common/extensions/extension_icon_set.h"
43 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
44 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
45 #include "chrome/common/render_messages.h"
46 #include "chrome/common/url_constants.h"
47 #include "content/public/browser/invalidate_type.h"
48 #include "content/public/browser/navigation_controller.h"
49 #include "content/public/browser/navigation_details.h"
50 #include "content/public/browser/navigation_entry.h"
51 #include "content/public/browser/notification_service.h"
52 #include "content/public/browser/notification_source.h"
53 #include "content/public/browser/notification_types.h"
54 #include "content/public/browser/render_process_host.h"
55 #include "content/public/browser/render_view_host.h"
56 #include "content/public/browser/render_widget_host_view.h"
57 #include "content/public/browser/web_contents.h"
58 #include "content/public/browser/web_contents_view.h"
59 #include "content/public/common/frame_navigate_params.h"
60 #include "extensions/browser/extension_error.h"
61 #include "extensions/browser/extension_registry.h"
62 #include "extensions/browser/extension_system.h"
63 #include "extensions/common/extension.h"
64 #include "extensions/common/extension_messages.h"
65 #include "extensions/common/extension_resource.h"
66 #include "extensions/common/extension_urls.h"
67 #include "extensions/common/feature_switch.h"
68
69 #if defined(OS_CHROMEOS)
70 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
71 #endif
72
73 using content::NavigationController;
74 using content::NavigationEntry;
75 using content::RenderViewHost;
76 using content::WebContents;
77
78 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
79
80 namespace extensions {
81
82 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
83     TabHelper* tab_helper)
84     : tab_helper_(tab_helper) {
85   tab_helper_->AddScriptExecutionObserver(this);
86 }
87
88 TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
89     : tab_helper_(NULL) {
90 }
91
92 TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
93   if (tab_helper_)
94     tab_helper_->RemoveScriptExecutionObserver(this);
95 }
96
97 TabHelper::TabHelper(content::WebContents* web_contents)
98     : content::WebContentsObserver(web_contents),
99       extension_app_(NULL),
100       extension_function_dispatcher_(
101           Profile::FromBrowserContext(web_contents->GetBrowserContext()), this),
102       pending_web_app_action_(NONE),
103       script_executor_(new ScriptExecutor(web_contents,
104                                           &script_execution_observers_)),
105       location_bar_controller_(new PageActionController(web_contents)),
106       image_loader_ptr_factory_(this),
107       webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) {
108   // The ActiveTabPermissionManager requires a session ID; ensure this
109   // WebContents has one.
110   SessionTabHelper::CreateForWebContents(web_contents);
111   if (web_contents->GetRenderViewHost())
112     SetTabId(web_contents->GetRenderViewHost());
113   active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
114       web_contents,
115       SessionID::IdForTab(web_contents),
116       Profile::FromBrowserContext(web_contents->GetBrowserContext())));
117
118   // If more classes need to listen to global content script activity, then
119   // a separate routing class with an observer interface should be written.
120   profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
121
122 #if defined(ENABLE_EXTENSIONS)
123   AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
124 #endif
125
126   registrar_.Add(this,
127                  content::NOTIFICATION_LOAD_STOP,
128                  content::Source<NavigationController>(
129                      &web_contents->GetController()));
130 }
131
132 TabHelper::~TabHelper() {
133 #if defined(ENABLE_EXTENSIONS)
134   RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
135 #endif
136 }
137
138 void TabHelper::CreateApplicationShortcuts() {
139   DCHECK(CanCreateApplicationShortcuts());
140   NavigationEntry* entry =
141       web_contents()->GetController().GetLastCommittedEntry();
142   if (!entry)
143     return;
144
145   pending_web_app_action_ = CREATE_SHORTCUT;
146
147   // Start fetching web app info for CreateApplicationShortcut dialog and show
148   // the dialog when the data is available in OnDidGetApplicationInfo.
149   GetApplicationInfo(entry->GetPageID());
150 }
151
152 void TabHelper::CreateHostedAppFromWebContents() {
153   DCHECK(CanCreateApplicationShortcuts());
154   NavigationEntry* entry =
155       web_contents()->GetController().GetLastCommittedEntry();
156   if (!entry)
157     return;
158
159   pending_web_app_action_ = CREATE_HOSTED_APP;
160
161   // Start fetching web app info for CreateApplicationShortcut dialog and show
162   // the dialog when the data is available in OnDidGetApplicationInfo.
163   GetApplicationInfo(entry->GetPageID());
164 }
165
166 bool TabHelper::CanCreateApplicationShortcuts() const {
167 #if defined(OS_MACOSX)
168   return false;
169 #else
170   return web_app::IsValidUrl(web_contents()->GetURL()) &&
171       pending_web_app_action_ == NONE;
172 #endif
173 }
174
175 void TabHelper::SetExtensionApp(const Extension* extension) {
176   DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
177   if (extension_app_ == extension)
178     return;
179
180   extension_app_ = extension;
181
182   UpdateExtensionAppIcon(extension_app_);
183
184   content::NotificationService::current()->Notify(
185       chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
186       content::Source<TabHelper>(this),
187       content::NotificationService::NoDetails());
188 }
189
190 void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
191   const Extension* extension = GetExtension(extension_app_id);
192   if (extension)
193     SetExtensionApp(extension);
194 }
195
196 void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
197   const Extension* extension = GetExtension(extension_app_id);
198   if (extension)
199     UpdateExtensionAppIcon(extension);
200 }
201
202 SkBitmap* TabHelper::GetExtensionAppIcon() {
203   if (extension_app_icon_.empty())
204     return NULL;
205
206   return &extension_app_icon_;
207 }
208
209 void TabHelper::FinishCreateBookmarkApp(
210     const extensions::Extension* extension,
211     const WebApplicationInfo& web_app_info) {
212   pending_web_app_action_ = NONE;
213
214   // There was an error with downloading the icons or installing the app.
215   if (!extension)
216     return;
217
218 #if defined(OS_CHROMEOS)
219   ChromeLauncherController::instance()->PinAppWithID(extension->id());
220 #endif
221
222 // Android does not implement browser_finder.cc.
223 #if !defined(OS_ANDROID)
224   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
225   if (browser) {
226     browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id());
227   }
228 #endif
229 }
230
231 void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
232   SetTabId(render_view_host);
233 }
234
235 void TabHelper::DidNavigateMainFrame(
236     const content::LoadCommittedDetails& details,
237     const content::FrameNavigateParams& params) {
238 #if defined(ENABLE_EXTENSIONS)
239   if (ExtensionSystem::Get(profile_)->extension_service() &&
240       RulesRegistryService::Get(profile_)) {
241     RulesRegistryService::Get(profile_)->content_rules_registry()->
242         DidNavigateMainFrame(web_contents(), details, params);
243   }
244 #endif  // defined(ENABLE_EXTENSIONS)
245
246   Profile* profile =
247       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
248   ExtensionService* service = profile->GetExtensionService();
249   if (!service)
250     return;
251
252   if (CommandLine::ForCurrentProcess()->HasSwitch(
253           switches::kEnableStreamlinedHostedApps)) {
254 #if !defined(OS_ANDROID)
255     Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
256     if (browser && browser->is_app()) {
257       SetExtensionApp(service->GetInstalledExtension(
258           web_app::GetExtensionIdFromApplicationName(browser->app_name())));
259     } else {
260       UpdateExtensionAppIcon(service->GetInstalledExtensionByUrl(params.url));
261     }
262 #endif
263   } else {
264     UpdateExtensionAppIcon(service->GetInstalledExtensionByUrl(params.url));
265   }
266
267   if (details.is_in_page)
268     return;
269
270   ExtensionActionManager* extension_action_manager =
271       ExtensionActionManager::Get(profile);
272   for (ExtensionSet::const_iterator it = service->extensions()->begin();
273        it != service->extensions()->end(); ++it) {
274     ExtensionAction* browser_action =
275         extension_action_manager->GetBrowserAction(*it->get());
276     if (browser_action) {
277       browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
278       content::NotificationService::current()->Notify(
279           chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
280           content::Source<ExtensionAction>(browser_action),
281           content::NotificationService::NoDetails());
282     }
283   }
284 }
285
286 bool TabHelper::OnMessageReceived(const IPC::Message& message) {
287   bool handled = true;
288   IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
289     IPC_MESSAGE_HANDLER(ChromeExtensionHostMsg_DidGetApplicationInfo,
290                         OnDidGetApplicationInfo)
291     IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
292                         OnInlineWebstoreInstall)
293     IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
294                         OnGetAppInstallState);
295     IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
296     IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
297                         OnContentScriptsExecuting)
298     IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
299                         OnWatchedPageChange)
300     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
301                         OnDetailedConsoleMessageAdded)
302     IPC_MESSAGE_UNHANDLED(handled = false)
303   IPC_END_MESSAGE_MAP()
304   return handled;
305 }
306
307 void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
308                                          WebContents* new_web_contents) {
309   // When the WebContents that this is attached to is cloned, give the new clone
310   // a TabHelper and copy state over.
311   CreateForWebContents(new_web_contents);
312   TabHelper* new_helper = FromWebContents(new_web_contents);
313
314   new_helper->SetExtensionApp(extension_app());
315   new_helper->extension_app_icon_ = extension_app_icon_;
316 }
317
318 void TabHelper::OnDidGetApplicationInfo(int32 page_id,
319                                         const WebApplicationInfo& info) {
320   // Android does not implement BrowserWindow.
321 #if !defined(OS_MACOSX) && !defined(OS_ANDROID)
322   web_app_info_ = info;
323
324   NavigationEntry* entry =
325       web_contents()->GetController().GetLastCommittedEntry();
326   if (!entry || (entry->GetPageID() != page_id))
327     return;
328
329   switch (pending_web_app_action_) {
330     case CREATE_SHORTCUT: {
331       chrome::ShowCreateWebAppShortcutsDialog(
332           web_contents()->GetView()->GetTopLevelNativeWindow(),
333           web_contents());
334       break;
335     }
336     case CREATE_HOSTED_APP: {
337       if (web_app_info_.app_url.is_empty())
338         web_app_info_.app_url = web_contents()->GetURL();
339
340       if (web_app_info_.title.empty())
341         web_app_info_.title = web_contents()->GetTitle();
342       if (web_app_info_.title.empty())
343         web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());
344
345       bookmark_app_helper_.reset(new BookmarkAppHelper(
346           profile_->GetExtensionService(), web_app_info_, web_contents()));
347       bookmark_app_helper_->Create(base::Bind(
348           &TabHelper::FinishCreateBookmarkApp, base::Unretained(this)));
349       break;
350     }
351     case UPDATE_SHORTCUT: {
352       web_app::UpdateShortcutForTabContents(web_contents());
353       break;
354     }
355     default:
356       NOTREACHED();
357       break;
358   }
359
360   // The hosted app action will be cleared once the installation completes or
361   // fails.
362   if (pending_web_app_action_ != CREATE_HOSTED_APP)
363     pending_web_app_action_ = NONE;
364 #endif
365 }
366
367 void TabHelper::OnInlineWebstoreInstall(int install_id,
368                                         int return_route_id,
369                                         const std::string& webstore_item_id,
370                                         const GURL& requestor_url,
371                                         int listeners_mask) {
372 #if defined(ENABLE_EXTENSIONS)
373   // Check that the listener is reasonable. We should never get anything other
374   // than an install stage listener, a download listener, or both.
375   if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
376                           api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) {
377     NOTREACHED();
378     return;
379   }
380   // Inform the Webstore API that an inline install is happening, in case the
381   // page requested status updates.
382   Profile* profile =
383       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
384   WebstoreAPI::Get(profile)->OnInlineInstallStart(
385       return_route_id, this, webstore_item_id, listeners_mask);
386 #endif
387
388   WebstoreStandaloneInstaller::Callback callback =
389       base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this),
390                  install_id, return_route_id);
391   scoped_refptr<WebstoreInlineInstaller> installer(
392       webstore_inline_installer_factory_->CreateInstaller(
393           web_contents(),
394           webstore_item_id,
395           requestor_url,
396           callback));
397   installer->BeginInstall();
398 }
399
400 void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
401                                      int return_route_id,
402                                      int callback_id) {
403   ExtensionRegistry* registry =
404       ExtensionRegistry::Get(web_contents()->GetBrowserContext());
405   const ExtensionSet& extensions = registry->enabled_extensions();
406   const ExtensionSet& disabled_extensions = registry->disabled_extensions();
407
408   std::string state;
409   if (extensions.GetHostedAppByURL(requestor_url))
410     state = extension_misc::kAppStateInstalled;
411   else if (disabled_extensions.GetHostedAppByURL(requestor_url))
412     state = extension_misc::kAppStateDisabled;
413   else
414     state = extension_misc::kAppStateNotInstalled;
415
416   Send(new ExtensionMsg_GetAppInstallStateResponse(
417       return_route_id, state, callback_id));
418 }
419
420 void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
421   extension_function_dispatcher_.Dispatch(request,
422                                           web_contents()->GetRenderViewHost());
423 }
424
425 void TabHelper::OnContentScriptsExecuting(
426     const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
427     int32 on_page_id,
428     const GURL& on_url) {
429   FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_,
430                     OnScriptsExecuted(web_contents(),
431                                       executing_scripts_map,
432                                       on_page_id,
433                                       on_url));
434 }
435
436 void TabHelper::OnWatchedPageChange(
437     const std::vector<std::string>& css_selectors) {
438 #if defined(ENABLE_EXTENSIONS)
439   if (ExtensionSystem::Get(profile_)->extension_service() &&
440       RulesRegistryService::Get(profile_)) {
441     RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
442         web_contents(), css_selectors);
443   }
444 #endif  // defined(ENABLE_EXTENSIONS)
445 }
446
447 void TabHelper::OnDetailedConsoleMessageAdded(
448     const base::string16& message,
449     const base::string16& source,
450     const StackTrace& stack_trace,
451     int32 severity_level) {
452   if (IsSourceFromAnExtension(source)) {
453     content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
454     ErrorConsole::Get(profile_)->ReportError(
455         scoped_ptr<ExtensionError>(new RuntimeError(
456             extension_app_ ? extension_app_->id() : std::string(),
457             profile_->IsOffTheRecord(),
458             source,
459             message,
460             stack_trace,
461             web_contents() ?
462                 web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
463             static_cast<logging::LogSeverity>(severity_level),
464             rvh->GetRoutingID(),
465             rvh->GetProcess()->GetID())));
466   }
467 }
468
469 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
470   if (extension_app_id.empty())
471     return NULL;
472
473   Profile* profile =
474       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
475   ExtensionService* extension_service = profile->GetExtensionService();
476   if (!extension_service || !extension_service->is_ready())
477     return NULL;
478
479   const Extension* extension =
480       extension_service->GetExtensionById(extension_app_id, false);
481   return extension;
482 }
483
484 void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
485   extension_app_icon_.reset();
486   // Ensure previously enqueued callbacks are ignored.
487   image_loader_ptr_factory_.InvalidateWeakPtrs();
488
489   // Enqueue OnImageLoaded callback.
490   if (extension) {
491     Profile* profile =
492         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
493     extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile);
494     loader->LoadImageAsync(
495         extension,
496         IconsInfo::GetIconResource(extension,
497                                    extension_misc::EXTENSION_ICON_SMALL,
498                                    ExtensionIconSet::MATCH_BIGGER),
499         gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
500                   extension_misc::EXTENSION_ICON_SMALL),
501         base::Bind(&TabHelper::OnImageLoaded,
502                    image_loader_ptr_factory_.GetWeakPtr()));
503   }
504 }
505
506 void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
507   extension_app_icon_ = app_icon;
508   web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
509 }
510
511 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
512     WebstoreInlineInstallerFactory* factory) {
513   webstore_inline_installer_factory_.reset(factory);
514 }
515
516 void TabHelper::OnImageLoaded(const gfx::Image& image) {
517   if (!image.IsEmpty()) {
518     extension_app_icon_ = *image.ToSkBitmap();
519     web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
520   }
521 }
522
523 WindowController* TabHelper::GetExtensionWindowController() const  {
524   return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
525 }
526
527 void TabHelper::OnInlineInstallComplete(int install_id,
528                                         int return_route_id,
529                                         bool success,
530                                         const std::string& error) {
531   Send(new ExtensionMsg_InlineWebstoreInstallResponse(
532       return_route_id, install_id, success, success ? std::string() : error));
533 }
534
535 WebContents* TabHelper::GetAssociatedWebContents() const {
536   return web_contents();
537 }
538
539 void TabHelper::GetApplicationInfo(int32 page_id) {
540   Send(new ChromeExtensionMsg_GetApplicationInfo(routing_id(), page_id));
541 }
542
543 void TabHelper::Observe(int type,
544                         const content::NotificationSource& source,
545                         const content::NotificationDetails& details) {
546   switch (type) {
547     case content::NOTIFICATION_LOAD_STOP: {
548       const NavigationController& controller =
549           *content::Source<NavigationController>(source).ptr();
550       DCHECK_EQ(controller.GetWebContents(), web_contents());
551
552       if (pending_web_app_action_ == UPDATE_SHORTCUT) {
553         // Schedule a shortcut update when web application info is available if
554         // last committed entry is not NULL. Last committed entry could be NULL
555         // when an interstitial page is injected (e.g. bad https certificate,
556         // malware site etc). When this happens, we abort the shortcut update.
557         NavigationEntry* entry = controller.GetLastCommittedEntry();
558         if (entry)
559           GetApplicationInfo(entry->GetPageID());
560         else
561           pending_web_app_action_ = NONE;
562       }
563       break;
564     }
565   }
566 }
567
568 void TabHelper::SetTabId(RenderViewHost* render_view_host) {
569   render_view_host->Send(
570       new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
571                                 SessionID::IdForTab(web_contents())));
572 }
573
574 }  // namespace extensions