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