root/chrome/browser/extensions/tab_helper.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. webstore_inline_installer_factory_
  2. CreateApplicationShortcuts
  3. CreateHostedAppFromWebContents
  4. CanCreateApplicationShortcuts
  5. SetExtensionApp
  6. SetExtensionAppById
  7. SetExtensionAppIconById
  8. GetExtensionAppIcon
  9. FinishCreateBookmarkApp
  10. RenderViewCreated
  11. DidNavigateMainFrame
  12. OnMessageReceived
  13. DidCloneToNewWebContents
  14. OnDidGetApplicationInfo
  15. OnInlineWebstoreInstall
  16. OnGetAppInstallState
  17. OnRequest
  18. OnContentScriptsExecuting
  19. OnWatchedPageChange
  20. OnDetailedConsoleMessageAdded
  21. GetExtension
  22. UpdateExtensionAppIcon
  23. SetAppIcon
  24. SetWebstoreInlineInstallerFactoryForTests
  25. OnImageLoaded
  26. GetExtensionWindowController
  27. OnInlineInstallComplete
  28. GetAssociatedWebContents
  29. GetApplicationInfo
  30. Observe
  31. SetTabId

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/tab_helper.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/activity_log/activity_log.h"
#include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
#include "chrome/browser/extensions/api/webstore/webstore_api.h"
#include "chrome/browser/extensions/bookmark_app_helper.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/error_console/error_console.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/image_loader.h"
#include "chrome/browser/extensions/page_action_controller.h"
#include "chrome/browser/extensions/script_executor.h"
#include "chrome/browser/extensions/webstore_inline_installer.h"
#include "chrome/browser/extensions/webstore_inline_installer_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_id.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/chrome_extension_messages.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/frame_navigate_params.h"
#include "extensions/browser/extension_error.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/feature_switch.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
#endif

#if defined(OS_WIN)
#include "chrome/browser/web_applications/web_app_win.h"
#endif

using content::NavigationController;
using content::NavigationEntry;
using content::RenderViewHost;
using content::WebContents;

DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);

namespace extensions {

TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
    TabHelper* tab_helper)
    : tab_helper_(tab_helper) {
  tab_helper_->AddScriptExecutionObserver(this);
}

TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
    : tab_helper_(NULL) {
}

TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
  if (tab_helper_)
    tab_helper_->RemoveScriptExecutionObserver(this);
}

TabHelper::TabHelper(content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      extension_app_(NULL),
      extension_function_dispatcher_(
          Profile::FromBrowserContext(web_contents->GetBrowserContext()), this),
      pending_web_app_action_(NONE),
      script_executor_(new ScriptExecutor(web_contents,
                                          &script_execution_observers_)),
      location_bar_controller_(new PageActionController(web_contents)),
      image_loader_ptr_factory_(this),
      webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) {
  // The ActiveTabPermissionManager requires a session ID; ensure this
  // WebContents has one.
  SessionTabHelper::CreateForWebContents(web_contents);
  if (web_contents->GetRenderViewHost())
    SetTabId(web_contents->GetRenderViewHost());
  active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
      web_contents,
      SessionID::IdForTab(web_contents),
      Profile::FromBrowserContext(web_contents->GetBrowserContext())));

  // If more classes need to listen to global content script activity, then
  // a separate routing class with an observer interface should be written.
  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());

#if defined(ENABLE_EXTENSIONS)
  AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
#endif

  registrar_.Add(this,
                 content::NOTIFICATION_LOAD_STOP,
                 content::Source<NavigationController>(
                     &web_contents->GetController()));
}

TabHelper::~TabHelper() {
#if defined(ENABLE_EXTENSIONS)
  RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
#endif
}

void TabHelper::CreateApplicationShortcuts() {
  DCHECK(CanCreateApplicationShortcuts());
  NavigationEntry* entry =
      web_contents()->GetController().GetLastCommittedEntry();
  if (!entry)
    return;

  pending_web_app_action_ = CREATE_SHORTCUT;

  // Start fetching web app info for CreateApplicationShortcut dialog and show
  // the dialog when the data is available in OnDidGetApplicationInfo.
  GetApplicationInfo(entry->GetPageID());
}

void TabHelper::CreateHostedAppFromWebContents() {
  DCHECK(CanCreateApplicationShortcuts());
  NavigationEntry* entry =
      web_contents()->GetController().GetLastCommittedEntry();
  if (!entry)
    return;

  pending_web_app_action_ = CREATE_HOSTED_APP;

  // Start fetching web app info for CreateApplicationShortcut dialog and show
  // the dialog when the data is available in OnDidGetApplicationInfo.
  GetApplicationInfo(entry->GetPageID());
}

bool TabHelper::CanCreateApplicationShortcuts() const {
#if defined(OS_MACOSX)
  return false;
#else
  return web_app::IsValidUrl(web_contents()->GetURL()) &&
      pending_web_app_action_ == NONE;
#endif
}

void TabHelper::SetExtensionApp(const Extension* extension) {
  DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
  if (extension_app_ == extension)
    return;

  extension_app_ = extension;

  UpdateExtensionAppIcon(extension_app_);

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
      content::Source<TabHelper>(this),
      content::NotificationService::NoDetails());
}

void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
  const Extension* extension = GetExtension(extension_app_id);
  if (extension)
    SetExtensionApp(extension);
}

void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
  const Extension* extension = GetExtension(extension_app_id);
  if (extension)
    UpdateExtensionAppIcon(extension);
}

SkBitmap* TabHelper::GetExtensionAppIcon() {
  if (extension_app_icon_.empty())
    return NULL;

  return &extension_app_icon_;
}

void TabHelper::FinishCreateBookmarkApp(
    const extensions::Extension* extension,
    const WebApplicationInfo& web_app_info) {
  pending_web_app_action_ = NONE;

  // There was an error with downloading the icons or installing the app.
  if (!extension)
    return;

#if defined(OS_CHROMEOS)
  ChromeLauncherController::instance()->PinAppWithID(extension->id());
#endif

// Android does not implement browser_finder.cc.
#if !defined(OS_ANDROID)
  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
  if (browser) {
    browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id());
  }
#endif
}

void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
  SetTabId(render_view_host);
}

void TabHelper::DidNavigateMainFrame(
    const content::LoadCommittedDetails& details,
    const content::FrameNavigateParams& params) {
#if defined(ENABLE_EXTENSIONS)
  if (ExtensionSystem::Get(profile_)->extension_service() &&
      RulesRegistryService::Get(profile_)) {
    RulesRegistryService::Get(profile_)->content_rules_registry()->
        DidNavigateMainFrame(web_contents(), details, params);
  }
#endif  // defined(ENABLE_EXTENSIONS)

  content::BrowserContext* context = web_contents()->GetBrowserContext();
  ExtensionRegistry* registry = ExtensionRegistry::Get(context);
  const ExtensionSet& enabled_extensions = registry->enabled_extensions();

  if (CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnableStreamlinedHostedApps)) {
#if !defined(OS_ANDROID)
    Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
    if (browser && browser->is_app()) {
      SetExtensionApp(registry->GetExtensionById(
          web_app::GetExtensionIdFromApplicationName(browser->app_name()),
          ExtensionRegistry::EVERYTHING));
    } else {
      UpdateExtensionAppIcon(
          enabled_extensions.GetExtensionOrAppByURL(params.url));
    }
#endif
  } else {
    UpdateExtensionAppIcon(
        enabled_extensions.GetExtensionOrAppByURL(params.url));
  }

  if (details.is_in_page)
    return;

  ExtensionActionManager* extension_action_manager =
      ExtensionActionManager::Get(Profile::FromBrowserContext(context));
  for (ExtensionSet::const_iterator it = enabled_extensions.begin();
       it != enabled_extensions.end();
       ++it) {
    ExtensionAction* browser_action =
        extension_action_manager->GetBrowserAction(*it->get());
    if (browser_action) {
      browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
      content::NotificationService::current()->Notify(
          chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
          content::Source<ExtensionAction>(browser_action),
          content::NotificationService::NoDetails());
    }
  }
}

bool TabHelper::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
    IPC_MESSAGE_HANDLER(ChromeExtensionHostMsg_DidGetApplicationInfo,
                        OnDidGetApplicationInfo)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
                        OnInlineWebstoreInstall)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
                        OnGetAppInstallState);
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
                        OnContentScriptsExecuting)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
                        OnWatchedPageChange)
    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
                        OnDetailedConsoleMessageAdded)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
                                         WebContents* new_web_contents) {
  // When the WebContents that this is attached to is cloned, give the new clone
  // a TabHelper and copy state over.
  CreateForWebContents(new_web_contents);
  TabHelper* new_helper = FromWebContents(new_web_contents);

  new_helper->SetExtensionApp(extension_app());
  new_helper->extension_app_icon_ = extension_app_icon_;
}

void TabHelper::OnDidGetApplicationInfo(int32 page_id,
                                        const WebApplicationInfo& info) {
  // Android does not implement BrowserWindow.
#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
  web_app_info_ = info;

  NavigationEntry* entry =
      web_contents()->GetController().GetLastCommittedEntry();
  if (!entry || (entry->GetPageID() != page_id))
    return;

  switch (pending_web_app_action_) {
    case CREATE_SHORTCUT: {
      chrome::ShowCreateWebAppShortcutsDialog(
          web_contents()->GetView()->GetTopLevelNativeWindow(),
          web_contents());
      break;
    }
    case CREATE_HOSTED_APP: {
      if (web_app_info_.app_url.is_empty())
        web_app_info_.app_url = web_contents()->GetURL();

      if (web_app_info_.title.empty())
        web_app_info_.title = web_contents()->GetTitle();
      if (web_app_info_.title.empty())
        web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());

      bookmark_app_helper_.reset(new BookmarkAppHelper(
          profile_->GetExtensionService(), web_app_info_, web_contents()));
      bookmark_app_helper_->Create(base::Bind(
          &TabHelper::FinishCreateBookmarkApp, base::Unretained(this)));
      break;
    }
    case UPDATE_SHORTCUT: {
      web_app::UpdateShortcutForTabContents(web_contents());
      break;
    }
    default:
      NOTREACHED();
      break;
  }

  // The hosted app action will be cleared once the installation completes or
  // fails.
  if (pending_web_app_action_ != CREATE_HOSTED_APP)
    pending_web_app_action_ = NONE;
#endif
}

void TabHelper::OnInlineWebstoreInstall(int install_id,
                                        int return_route_id,
                                        const std::string& webstore_item_id,
                                        const GURL& requestor_url,
                                        int listeners_mask) {
#if defined(ENABLE_EXTENSIONS)
  // Check that the listener is reasonable. We should never get anything other
  // than an install stage listener, a download listener, or both.
  if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
                          api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) {
    NOTREACHED();
    return;
  }
  // Inform the Webstore API that an inline install is happening, in case the
  // page requested status updates.
  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  WebstoreAPI::Get(profile)->OnInlineInstallStart(
      return_route_id, this, webstore_item_id, listeners_mask);
#endif

  WebstoreStandaloneInstaller::Callback callback =
      base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this),
                 install_id, return_route_id);
  scoped_refptr<WebstoreInlineInstaller> installer(
      webstore_inline_installer_factory_->CreateInstaller(
          web_contents(),
          webstore_item_id,
          requestor_url,
          callback));
  installer->BeginInstall();
}

void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
                                     int return_route_id,
                                     int callback_id) {
  ExtensionRegistry* registry =
      ExtensionRegistry::Get(web_contents()->GetBrowserContext());
  const ExtensionSet& extensions = registry->enabled_extensions();
  const ExtensionSet& disabled_extensions = registry->disabled_extensions();

  std::string state;
  if (extensions.GetHostedAppByURL(requestor_url))
    state = extension_misc::kAppStateInstalled;
  else if (disabled_extensions.GetHostedAppByURL(requestor_url))
    state = extension_misc::kAppStateDisabled;
  else
    state = extension_misc::kAppStateNotInstalled;

  Send(new ExtensionMsg_GetAppInstallStateResponse(
      return_route_id, state, callback_id));
}

void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
  extension_function_dispatcher_.Dispatch(request,
                                          web_contents()->GetRenderViewHost());
}

void TabHelper::OnContentScriptsExecuting(
    const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
    int32 on_page_id,
    const GURL& on_url) {
  FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_,
                    OnScriptsExecuted(web_contents(),
                                      executing_scripts_map,
                                      on_page_id,
                                      on_url));
}

void TabHelper::OnWatchedPageChange(
    const std::vector<std::string>& css_selectors) {
#if defined(ENABLE_EXTENSIONS)
  if (ExtensionSystem::Get(profile_)->extension_service() &&
      RulesRegistryService::Get(profile_)) {
    RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
        web_contents(), css_selectors);
  }
#endif  // defined(ENABLE_EXTENSIONS)
}

void TabHelper::OnDetailedConsoleMessageAdded(
    const base::string16& message,
    const base::string16& source,
    const StackTrace& stack_trace,
    int32 severity_level) {
  if (IsSourceFromAnExtension(source)) {
    content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
    ErrorConsole::Get(profile_)->ReportError(
        scoped_ptr<ExtensionError>(new RuntimeError(
            extension_app_ ? extension_app_->id() : std::string(),
            profile_->IsOffTheRecord(),
            source,
            message,
            stack_trace,
            web_contents() ?
                web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
            static_cast<logging::LogSeverity>(severity_level),
            rvh->GetRoutingID(),
            rvh->GetProcess()->GetID())));
  }
}

const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
  if (extension_app_id.empty())
    return NULL;

  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  ExtensionService* extension_service = profile->GetExtensionService();
  if (!extension_service || !extension_service->is_ready())
    return NULL;

  const Extension* extension =
      extension_service->GetExtensionById(extension_app_id, false);
  return extension;
}

void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
  extension_app_icon_.reset();
  // Ensure previously enqueued callbacks are ignored.
  image_loader_ptr_factory_.InvalidateWeakPtrs();

  // Enqueue OnImageLoaded callback.
  if (extension) {
    Profile* profile =
        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile);
    loader->LoadImageAsync(
        extension,
        IconsInfo::GetIconResource(extension,
                                   extension_misc::EXTENSION_ICON_SMALL,
                                   ExtensionIconSet::MATCH_BIGGER),
        gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
                  extension_misc::EXTENSION_ICON_SMALL),
        base::Bind(&TabHelper::OnImageLoaded,
                   image_loader_ptr_factory_.GetWeakPtr()));
  }
}

void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
  extension_app_icon_ = app_icon;
  web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
}

void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
    WebstoreInlineInstallerFactory* factory) {
  webstore_inline_installer_factory_.reset(factory);
}

void TabHelper::OnImageLoaded(const gfx::Image& image) {
  if (!image.IsEmpty()) {
    extension_app_icon_ = *image.ToSkBitmap();
    web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
  }
}

WindowController* TabHelper::GetExtensionWindowController() const  {
  return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
}

void TabHelper::OnInlineInstallComplete(int install_id,
                                        int return_route_id,
                                        bool success,
                                        const std::string& error) {
  Send(new ExtensionMsg_InlineWebstoreInstallResponse(
      return_route_id, install_id, success, success ? std::string() : error));
}

WebContents* TabHelper::GetAssociatedWebContents() const {
  return web_contents();
}

void TabHelper::GetApplicationInfo(int32 page_id) {
  Send(new ChromeExtensionMsg_GetApplicationInfo(routing_id(), page_id));
}

void TabHelper::Observe(int type,
                        const content::NotificationSource& source,
                        const content::NotificationDetails& details) {
  switch (type) {
    case content::NOTIFICATION_LOAD_STOP: {
      const NavigationController& controller =
          *content::Source<NavigationController>(source).ptr();
      DCHECK_EQ(controller.GetWebContents(), web_contents());

      if (pending_web_app_action_ == UPDATE_SHORTCUT) {
        // Schedule a shortcut update when web application info is available if
        // last committed entry is not NULL. Last committed entry could be NULL
        // when an interstitial page is injected (e.g. bad https certificate,
        // malware site etc). When this happens, we abort the shortcut update.
        NavigationEntry* entry = controller.GetLastCommittedEntry();
        if (entry)
          GetApplicationInfo(entry->GetPageID());
        else
          pending_web_app_action_ = NONE;
      }
      break;
    }
  }
}

void TabHelper::SetTabId(RenderViewHost* render_view_host) {
  render_view_host->Send(
      new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
                                SessionID::IdForTab(web_contents())));
}

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */