Makes tab contents look for extension whose extent contains the
current url and fetches the smallish icon if the extension is found.

BUG=none
TEST=none

Review URL: http://codereview.chromium.org/1576006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43345 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index b56d5b6..de783660 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -955,8 +955,16 @@
 }
 
 Extension* ExtensionsService::GetExtensionByURL(const GURL& url) {
-  std::string host = url.host();
-  return GetExtensionById(host, false);
+  return url.scheme() != chrome::kExtensionScheme ? NULL :
+      GetExtensionById(url.host(), false);
+}
+
+Extension* ExtensionsService::GetExtensionByWebExtent(const GURL& url) {
+  for (size_t i = 0; i < extensions_.size(); ++i) {
+    if (extensions_[i]->web_extent().ContainsURL(url))
+      return extensions_[i];
+  }
+  return NULL;
 }
 
 void ExtensionsService::ClearProvidersForTesting() {
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
index b56d7bd..88565166 100644
--- a/chrome/browser/extensions/extensions_service.h
+++ b/chrome/browser/extensions/extensions_service.h
@@ -250,9 +250,13 @@
   // Scan the extension directory and clean up the cruft.
   void GarbageCollectExtensions();
 
-  // Lookup an extension by |url|.  This uses the host of the URL as the id.
+  // Lookup an extension by |url|.
   Extension* GetExtensionByURL(const GURL& url);
 
+  // If there is an extension for the specified url it is returned. Otherwise
+  // returns the extension whose web extent contains |url|.
+  Extension* GetExtensionByWebExtent(const GURL& url);
+
   // Clear all ExternalExtensionProviders.
   void ClearProvidersForTesting();
 
diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc
index dd53dcc..10da50e 100644
--- a/chrome/browser/tab_contents/tab_contents.cc
+++ b/chrome/browser/tab_contents/tab_contents.cc
@@ -273,6 +273,8 @@
       last_search_case_sensitive_(false),
       last_search_prepopulate_text_(NULL),
       last_search_result_(),
+      app_extension_(NULL),
+      app_extension_for_current_page_(NULL),
       capturing_contents_(false),
       is_being_destroyed_(false),
       notify_disconnection_(false),
@@ -285,7 +287,6 @@
       is_showing_before_unload_dialog_(false),
       renderer_preferences_(),
       opener_dom_ui_type_(DOMUIFactory::kNoDOMUI),
-      app_extension_(NULL),
       language_state_(&controller_) {
   ClearBlockedContentSettings();
   renderer_preferences_util::UpdateFromSystemSettings(
@@ -333,6 +334,14 @@
   registrar_.Add(this, NotificationType::CONTENT_SETTINGS_CHANGED,
                  NotificationService::AllSources());
 
+  // Listen for extension changes so we can update extension_for_current_page_.
+  registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+                 NotificationService::AllSources());
+  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
+                 NotificationService::AllSources());
+  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED,
+                 NotificationService::AllSources());
+
   // Keep a global copy of the previous search string (if any).
   static string16 global_last_search = string16();
   last_search_prepopulate_text_ = &global_last_search;
@@ -483,6 +492,8 @@
   DCHECK(!extension || extension->GetFullLaunchURL().is_valid());
   app_extension_ = extension;
 
+  UpdateAppExtensionIcon(app_extension_);
+
   NotificationService::current()->Notify(
       NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
       Source<TabContents>(this),
@@ -786,6 +797,10 @@
       favicon_service->SetFaviconOutOfDateForPage(entry.url());
   }
 
+  // The url likely changed, see if there is an extension whose extent contains
+  // the current page.
+  UpdateAppExtensionForCurrentPage();
+
   return true;
 }
 
@@ -1543,6 +1558,10 @@
   // Clear the cache of forms in AutoFill.
   if (autofill_manager_.get())
     autofill_manager_->Reset();
+
+  // The url likely changed, see if there is an extension whose extent contains
+  // the current page.
+  UpdateAppExtensionForCurrentPage();
 }
 
 void TabContents::DidNavigateAnyFramePostCommit(
@@ -2888,11 +2907,81 @@
       break;
     }
 
+    case NotificationType::EXTENSION_LOADED: {
+      if (!app_extension_ && !app_extension_for_current_page_ &&
+          Source<Profile>(source).ptr() == profile()) {
+        UpdateAppExtensionForCurrentPage();
+        if (app_extension_for_current_page_)
+          NotifyNavigationStateChanged(INVALIDATE_TAB);
+      }
+      break;
+    }
+
+    case NotificationType::EXTENSION_UNLOADED:
+    case NotificationType::EXTENSION_UNLOADED_DISABLED: {
+      if (app_extension_for_current_page_ ==
+          Details<Extension>(details).ptr()) {
+        app_extension_for_current_page_ = NULL;
+        UpdateAppExtensionForCurrentPage();
+        NotifyNavigationStateChanged(INVALIDATE_TAB);
+      }
+      break;
+    }
+
     default:
       NOTREACHED();
   }
 }
 
+void TabContents::UpdateAppExtensionIcon(Extension* extension) {
+  app_extension_icon_.reset();
+  if (extension) {
+    app_extension_image_loader_.reset(new ImageLoadingTracker(this));
+    app_extension_image_loader_->LoadImage(
+        extension,
+        extension->GetIconPath(Extension::EXTENSION_ICON_SMALLISH),
+        gfx::Size(Extension::EXTENSION_ICON_SMALLISH,
+                  Extension::EXTENSION_ICON_SMALLISH),
+        ImageLoadingTracker::CACHE);
+  } else {
+    app_extension_image_loader_.reset(NULL);
+  }
+}
+
+Extension* TabContents::GetExtensionContaining(const GURL& url) {
+  ExtensionsService* extensions_service = profile()->GetExtensionsService();
+  if (!extensions_service)
+    return NULL;
+
+  Extension* extension = extensions_service->GetExtensionByURL(url);
+  return extension ?
+      extension : extensions_service->GetExtensionByWebExtent(url);
+}
+
+void TabContents::UpdateAppExtensionForCurrentPage() {
+  if (app_extension_) {
+    // Tab has an explicit app extension; nothing to do.
+    return;
+  }
+
+  // Check the current extension before iterating through all extensions.
+  if (app_extension_for_current_page_ &&
+      app_extension_for_current_page_->web_extent().ContainsURL(GetURL())) {
+    return;
+  }
+
+  app_extension_for_current_page_ = GetExtensionContaining(GetURL());
+  UpdateAppExtensionIcon(app_extension_for_current_page_);
+}
+
+void TabContents::OnImageLoaded(SkBitmap* image, ExtensionResource resource,
+                                int index) {
+  if (image) {
+    app_extension_icon_ = *image;
+    NotifyNavigationStateChanged(INVALIDATE_TAB);
+  }
+}
+
 std::wstring TabContents::GetMessageBoxTitle(const GURL& frame_url,
                                              bool is_alert) {
   if (!frame_url.has_host())
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
index 62547d6..de11dd241 100644
--- a/chrome/browser/tab_contents/tab_contents.h
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -19,6 +19,7 @@
 #include "chrome/browser/cancelable_request.h"
 #include "chrome/browser/dom_ui/dom_ui_factory.h"
 #include "chrome/browser/download/save_package.h"
+#include "chrome/browser/extensions/image_loading_tracker.h"
 #include "chrome/browser/fav_icon_helper.h"
 #include "chrome/browser/find_bar_controller.h"
 #include "chrome/browser/find_notification_details.h"
@@ -100,14 +101,15 @@
                     public RenderViewHostDelegate::Resource,
                     public RenderViewHostManager::Delegate,
                     public SelectFileDialog::Listener,
-                    public JavaScriptMessageBoxClient {
+                    public JavaScriptMessageBoxClient,
+                    public ImageLoadingTracker::Observer {
  public:
   // Flags passed to the TabContentsDelegate.NavigationStateChanged to tell it
   // what has changed. Combine them to update more than one thing.
   enum InvalidateTypes {
     INVALIDATE_URL             = 1 << 0,  // The URL has changed.
-    INVALIDATE_TAB             = 1 << 1,  // The favicon, or crashed state
-                                          // changed.
+    INVALIDATE_TAB             = 1 << 1,  // The favicon, app icon, or crashed
+                                          // state changed.
     INVALIDATE_LOAD            = 1 << 2,  // The loading state has changed.
     INVALIDATE_PAGE_ACTIONS    = 1 << 3,  // Page action icons have changed.
     INVALIDATE_BOOKMARK_BAR    = 1 << 4,  // State of ShouldShowBookmarkBar
@@ -181,6 +183,8 @@
     return fav_icon_helper_;
   }
 
+  // App extensions ------------------------------------------------------------
+
   // Sets the extension denoting this as an app. If |extension| is non-null this
   // tab becomes an app-tab. TabContents does not listen for unload events for
   // the extension. It's up to consumers of TabContents to do that.
@@ -198,10 +202,14 @@
   Extension* app_extension() const { return app_extension_; }
   bool is_app() const { return app_extension_ != NULL; }
 
-#ifdef UNIT_TEST
-  // Expose the render manager for testing.
-  RenderViewHostManager* render_manager() { return &render_manager_; }
-#endif
+  // If an app extension has been explicitly set for this TabContents its icon
+  // is returned. If an app extension has not been set but there is an
+  // extension whose extent contains the url of the current page it's icon
+  // is returned. Otherwise an empty icon is returned.
+  //
+  // NOTE: the returned icon is larger than 16x16 (it's size is
+  // Extension::EXTENSION_ICON_SMALLISH).
+  const SkBitmap& app_extension_icon() const { return app_extension_icon_; }
 
   // Tab navigation state ------------------------------------------------------
 
@@ -281,9 +289,7 @@
     return web_app_info_;
   }
 
-  SkBitmap app_icon() const {
-    return app_icon_;
-  }
+  const SkBitmap& app_icon() const { return app_icon_; }
 
   // Sets an app icon associated with TabContents and fires an INVALIDATE_TITLE
   // navigation state change to trigger repaint of title.
@@ -329,6 +335,11 @@
   virtual void ShowContents();
   virtual void HideContents();
 
+#ifdef UNIT_TEST
+  // Expose the render manager for testing.
+  RenderViewHostManager* render_manager() { return &render_manager_; }
+#endif
+
   // Commands ------------------------------------------------------------------
 
   // Implementation of PageNavigator.
@@ -1001,6 +1012,22 @@
                        const NotificationSource& source,
                        const NotificationDetails& details);
 
+  // App extensions related methods:
+
+  // Returns the first extension whose extent contains |url|.
+  Extension* GetExtensionContaining(const GURL& url);
+
+  // Resets app_icon_ and if |extension| is non-null creates a new
+  // ImageLoadingTracker to load the extension's image.
+  void UpdateAppExtensionIcon(Extension* extension);
+
+  // Called on every navigation to update app_icon_cache_entry_ as necessary.
+  void UpdateAppExtensionForCurrentPage();
+
+  // ImageLoadingTracker::Observer.
+  virtual void OnImageLoaded(SkBitmap* image, ExtensionResource resource,
+                             int index);
+
   // Data for core operation ---------------------------------------------------
 
   // Delegate for notifying our owner about stuff. Not owned by us.
@@ -1158,6 +1185,22 @@
   // information to build its presentation.
   FindNotificationDetails last_search_result_;
 
+  // Data for app extensions ---------------------------------------------------
+
+  // If non-null this tab is an app tab and this is the extension the tab was
+  // created for.
+  Extension* app_extension_;
+
+  // If app_extension_ is NULL and there is an extension whose extent contains
+  // the current url, this is the extension.
+  Extension* app_extension_for_current_page_;
+
+  // Icon for app_extension_ (if non-null) or extension_for_current_page_.
+  SkBitmap app_extension_icon_;
+
+  // Used for loading app_extension_icon_.
+  scoped_ptr<ImageLoadingTracker> app_extension_image_loader_;
+
   // Data for misc internal state ----------------------------------------------
 
   // See capturing_contents() above.
@@ -1217,10 +1260,6 @@
   // profile
   scoped_refptr<URLRequestContextGetter> request_context_;
 
-  // If non-null this tab is an app tab and this is the extension the tab was
-  // created for.
-  Extension* app_extension_;
-
   // Information about the language the page is in and has been translated to.
   LanguageState language_state_;