Add content script activity to the extension activity log.


BUG=39802


Review URL: https://chromiumcodereview.appspot.com/10905245

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@159040 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 1604b46..f095c58 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5278,6 +5278,9 @@
       <message name="IDS_EXTENSION_ACTIVITY_API_BLOCK" desc="Text for the label next to an activity message that represents a blocked call to the extension API.">
         Blocked API Call
       </message>
+      <message name="IDS_EXTENSION_ACTIVITY_CONTENT_SCRIPT" desc="Text for the label next to an activity message that represents a content script injection.">
+        Content Script
+      </message>
 
       <!-- chrome://extension-info bubble -->
       <message name="IDS_EXTENSION_SCRIPT_POPUP_IS_RUNNING" desc="The label in the extension info bubble that indicates the extension is running scripts on this page">
diff --git a/chrome/browser/extensions/activity_log.cc b/chrome/browser/extensions/activity_log.cc
index 937ee84e4..877db44 100644
--- a/chrome/browser/extensions/activity_log.cc
+++ b/chrome/browser/extensions/activity_log.cc
@@ -6,8 +6,14 @@
 
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/extension.h"
+#include "content/public/browser/web_contents.h"
+#include "googleurl/src/gurl.h"
 
 namespace extensions {
 
@@ -55,17 +61,52 @@
 
 void ActivityLog::Log(const Extension* extension,
                       Activity activity,
-                      const std::string& msg) const {
+                      const std::string& message) const {
+  std::vector<std::string> messages(1, message);
+  Log(extension, activity, messages);
+}
+
+void ActivityLog::Log(const Extension* extension,
+                      Activity activity,
+                      const std::vector<std::string>& messages) const {
   base::AutoLock scoped_lock(lock_);
 
   ObserverMap::const_iterator iter = observers_.find(extension);
   if (iter != observers_.end()) {
     iter->second->Notify(&Observer::OnExtensionActivity, extension, activity,
-                         msg);
+                         messages);
   }
 
   if (log_activity_to_stdout_) {
-    LOG(INFO) << extension->id() + ":" + ActivityToString(activity) + ":" + msg;
+    LOG(INFO) << extension->id() << ":" << ActivityToString(activity) << ":" <<
+        JoinString(messages, ' ');
+  }
+}
+
+void ActivityLog::OnContentScriptsExecuting(
+    const content::WebContents* web_contents,
+    const ExecutingScriptsMap& extension_ids,
+    int32 on_page_id,
+    const GURL& on_url) {
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  const ExtensionService* extension_service =
+      ExtensionSystem::Get(profile)->extension_service();
+  const ExtensionSet* extensions = extension_service->extensions();
+
+  for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
+       it != extension_ids.end(); ++it) {
+    const Extension* extension = extensions->GetByID(it->first);
+    if (!extension || !HasObservers(extension))
+      continue;
+
+    for (std::set<std::string>::const_iterator it2 = it->second.begin();
+         it2 != it->second.end(); ++it2) {
+      std::vector<std::string> messages;
+      messages.push_back(on_url.spec());
+      messages.push_back(*it2);
+      Log(extension, ActivityLog::ACTIVITY_CONTENT_SCRIPT, messages);
+    }
   }
 }
 
@@ -76,6 +117,8 @@
       return "api_call";
     case ActivityLog::ACTIVITY_EXTENSION_API_BLOCK:
       return "api_block";
+    case ActivityLog::ACTIVITY_CONTENT_SCRIPT:
+      return "content_script";
     default:
       NOTREACHED();
       return "";
diff --git a/chrome/browser/extensions/activity_log.h b/chrome/browser/extensions/activity_log.h
index d9c459a..f24000d9 100644
--- a/chrome/browser/extensions/activity_log.h
+++ b/chrome/browser/extensions/activity_log.h
@@ -7,31 +7,35 @@
 
 #include <map>
 #include <string>
+#include <vector>
 
 #include "base/memory/singleton.h"
 #include "base/observer_list_threadsafe.h"
 #include "base/synchronization/lock.h"
+#include "chrome/browser/extensions/tab_helper.h"
 
 namespace extensions {
 class Extension;
 
 // A utility for tracing interesting activity for each extension.
-class ActivityLog {
+class ActivityLog : public TabHelper::ContentScriptObserver {
  public:
   enum Activity {
-    ACTIVITY_EXTENSION_API_CALL,  // Extension API invocation is called.
-    ACTIVITY_EXTENSION_API_BLOCK  // Extension API invocation is blocked.
+    ACTIVITY_EXTENSION_API_CALL,   // Extension API invocation is called.
+    ACTIVITY_EXTENSION_API_BLOCK,  // Extension API invocation is blocked.
+    ACTIVITY_CONTENT_SCRIPT        // Content script is executing.
   };
 
   // Observers can listen for activity events.
   class Observer {
    public:
-    virtual void OnExtensionActivity(const Extension* extension,
-                                     Activity activity,
-                                     const std::string& msg) = 0;
+    virtual void OnExtensionActivity(
+        const Extension* extension,
+        Activity activity,
+        const std::vector<std::string>& messages) = 0;
   };
 
-  ~ActivityLog();
+  virtual ~ActivityLog();
   static ActivityLog* GetInstance();
 
   // Add/remove observer.
@@ -45,12 +49,22 @@
   // Log |activity| for |extension|.
   void Log(const Extension* extension,
            Activity activity,
-           const std::string& msg) const;
+           const std::string& message) const;
+  void Log(const Extension* extension,
+           Activity activity,
+           const std::vector<std::string>& messages) const;
 
  private:
   ActivityLog();
   friend struct DefaultSingletonTraits<ActivityLog>;
 
+  // TabHelper::ContentScriptObserver implementation.
+  virtual void OnContentScriptsExecuting(
+      const content::WebContents* web_contents,
+      const ExecutingScriptsMap& extension_ids,
+      int32 page_id,
+      const GURL& on_url) OVERRIDE;
+
   static const char* ActivityToString(Activity activity);
 
   // A lock used to synchronize access to member variables.
diff --git a/chrome/browser/extensions/script_badge_controller.cc b/chrome/browser/extensions/script_badge_controller.cc
index ff18157..427e1191b 100644
--- a/chrome/browser/extensions/script_badge_controller.cc
+++ b/chrome/browser/extensions/script_badge_controller.cc
@@ -30,8 +30,10 @@
 namespace extensions {
 
 ScriptBadgeController::ScriptBadgeController(content::WebContents* web_contents,
-                                             ScriptExecutor* script_executor)
+                                             ScriptExecutor* script_executor,
+                                             TabHelper* tab_helper)
     : ScriptExecutor::Observer(script_executor),
+      TabHelper::ContentScriptObserver(tab_helper),
       content::WebContentsObserver(web_contents) {
   Profile* profile =
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
@@ -136,6 +138,48 @@
   }
 }
 
+namespace {
+std::string JoinExtensionIDs(const ExecutingScriptsMap& ids) {
+  std::vector<std::string> as_vector;
+  for (ExecutingScriptsMap::const_iterator iter = ids.begin();
+       iter != ids.end(); ++iter) {
+    as_vector.push_back(iter->first);
+  }
+  return "[" + JoinString(as_vector, ',') + "]";
+}
+}  // namespace
+
+void ScriptBadgeController::OnContentScriptsExecuting(
+    const content::WebContents* web_contents,
+    const ExecutingScriptsMap& extension_ids,
+    int32 on_page_id,
+    const GURL& on_url) {
+  int32 current_page_id = GetPageID();
+  if (on_page_id != current_page_id)
+    return;
+
+  if (current_page_id < 0) {
+    // Tracking down http://crbug.com/138323.
+    std::string message = base::StringPrintf(
+        "Expected a page ID of %d but there was no navigation entry. "
+        "Extension IDs are %s.",
+        on_page_id,
+        JoinExtensionIDs(extension_ids).c_str());
+    char buf[1024];
+    base::snprintf(buf, arraysize(buf), "%s", message.c_str());
+    LOG(ERROR) << message;
+    return;
+  }
+
+  bool changed = false;
+  for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
+       it != extension_ids.end(); ++it) {
+    changed |= MarkExtensionExecuting(it->first);
+  }
+  if (changed)
+    NotifyChange();
+}
+
 ExtensionService* ScriptBadgeController::GetExtensionService() {
   TabContents* tab_contents = TabContents::FromWebContents(web_contents());
   return extensions::ExtensionSystem::Get(
@@ -176,53 +220,6 @@
     NotifyChange();
 }
 
-bool ScriptBadgeController::OnMessageReceived(const IPC::Message& message) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(ScriptBadgeController, message)
-    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
-                        OnContentScriptsExecuting)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-  return handled;
-}
-
-namespace {
-std::string JoinExtensionIDs(const std::set<std::string>& ids) {
-  std::vector<std::string> as_vector(ids.begin(), ids.end());
-  return "[" + JoinString(as_vector, ',') + "]";
-}
-}  // namespace
-
-void ScriptBadgeController::OnContentScriptsExecuting(
-    const std::set<std::string>& extension_ids,
-    int32 on_page_id,
-    const GURL& on_url) {
-  int32 current_page_id = GetPageID();
-  if (on_page_id != current_page_id)
-    return;
-
-  if (current_page_id < 0) {
-    // Tracking down http://crbug.com/138323.
-    std::string message = base::StringPrintf(
-        "Expected a page ID of %d but there was no navigation entry. "
-        "Extension IDs are %s.",
-        on_page_id,
-        JoinExtensionIDs(extension_ids).c_str());
-    char buf[1024];
-    base::snprintf(buf, arraysize(buf), "%s", message.c_str());
-    LOG(ERROR) << message;
-    return;
-  }
-
-  bool changed = false;
-  for (std::set<std::string>::const_iterator it = extension_ids.begin();
-       it != extension_ids.end(); ++it) {
-    changed |= MarkExtensionExecuting(*it);
-  }
-  if (changed)
-    NotifyChange();
-}
-
 ExtensionAction* ScriptBadgeController::AddExtensionToCurrentActions(
     const std::string& extension_id) {
   if (!extensions_in_current_actions_.insert(extension_id).second)
diff --git a/chrome/browser/extensions/script_badge_controller.h b/chrome/browser/extensions/script_badge_controller.h
index 13281fe..ec31f22 100644
--- a/chrome/browser/extensions/script_badge_controller.h
+++ b/chrome/browser/extensions/script_badge_controller.h
@@ -14,6 +14,7 @@
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/extensions/location_bar_controller.h"
 #include "chrome/browser/extensions/script_executor.h"
+#include "chrome/browser/extensions/tab_helper.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -49,11 +50,13 @@
 class ScriptBadgeController
     : public LocationBarController,
       public ScriptExecutor::Observer,
+      public TabHelper::ContentScriptObserver,
       public content::WebContentsObserver,
       public content::NotificationObserver {
  public:
   explicit ScriptBadgeController(content::WebContents* web_contents,
-                                 ScriptExecutor* script_executor);
+                                 ScriptExecutor* script_executor,
+                                 TabHelper* tab_helper);
   virtual ~ScriptBadgeController();
 
   // LocationBarController implementation.
@@ -71,6 +74,13 @@
       const GURL& on_url,
       const base::ListValue& script_result) OVERRIDE;
 
+  // TabHelper::ContentScriptObserver implementation.
+  virtual void OnContentScriptsExecuting(
+      const content::WebContents* web_contents,
+      const ExecutingScriptsMap& extension_ids,
+      int32 on_page_id,
+      const GURL& on_url) OVERRIDE;
+
  private:
   // Gets the ExtensionService for |tab_contents_|.
   ExtensionService* GetExtensionService();
@@ -82,18 +92,12 @@
   virtual void DidNavigateMainFrame(
       const content::LoadCommittedDetails& details,
       const content::FrameNavigateParams& params) OVERRIDE;
-  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
 
   // content::NotificationObserver implementation.
   virtual void Observe(int type,
                        const content::NotificationSource& source,
                        const content::NotificationDetails& details) OVERRIDE;
 
-  // IPC::Message handlers.
-  void OnContentScriptsExecuting(const std::set<std::string>& extension_ids,
-                                 int32 page_id,
-                                 const GURL& on_url);
-
   // Adds the extension's icon to the list of script badges.  Returns
   // the script badge ExtensionAction that was added, or NULL if
   // extension_id isn't valid.
diff --git a/chrome/browser/extensions/script_badge_controller_unittest.cc b/chrome/browser/extensions/script_badge_controller_unittest.cc
index 760d104f..0be5411 100644
--- a/chrome/browser/extensions/script_badge_controller_unittest.cc
+++ b/chrome/browser/extensions/script_badge_controller_unittest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/tab_contents/tab_contents.h"
 #include "chrome/browser/ui/tab_contents/test_tab_contents.h"
 #include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/chrome_version_info.h"
 #include "chrome/common/extensions/extension.h"
 #include "chrome/common/extensions/extension_builder.h"
@@ -41,6 +42,16 @@
         file_thread_(BrowserThread::FILE, MessageLoop::current()),
         current_channel_(chrome::VersionInfo::CHANNEL_DEV) {}
 
+  static void SetUpTestCase() {
+    old_command_line_ = *CommandLine::ForCurrentProcess();
+    CommandLine::ForCurrentProcess()->AppendSwitch(
+        switches::kEnableScriptBadges);
+  }
+
+  static void TearDownTestCase() {
+    *CommandLine::ForCurrentProcess() = old_command_line_;
+  }
+
   virtual void SetUp() OVERRIDE {
     // Note that this sets a PageActionController into the
     // extensions::TabHelper's location_bar_controller field.  Do
@@ -57,9 +68,9 @@
     extension_service_ = extension_system->CreateExtensionService(
         &command_line, FilePath(), false);
 
-    script_executor_.reset(new ScriptExecutor(web_contents()));
-    script_badge_controller_.reset(new ScriptBadgeController(
-        web_contents(), script_executor_.get()));
+    TabHelper::CreateForWebContents(web_contents());
+    script_badge_controller_ = static_cast<ScriptBadgeController*>(
+        TabHelper::FromWebContents(web_contents())->location_bar_controller());
   }
 
  protected:
@@ -80,15 +91,18 @@
   }
 
   ExtensionService* extension_service_;
-  scoped_ptr<ScriptExecutor> script_executor_;
-  scoped_ptr<ScriptBadgeController> script_badge_controller_;
+  ScriptBadgeController* script_badge_controller_;
 
  private:
+  static CommandLine old_command_line_;
   content::TestBrowserThread ui_thread_;
   content::TestBrowserThread file_thread_;
   Feature::ScopedCurrentChannel current_channel_;
 };
 
+CommandLine ScriptBadgeControllerTest::old_command_line_(
+    CommandLine::NO_PROGRAM);
+
 struct CountingNotificationObserver : public content::NotificationObserver {
   CountingNotificationObserver() : events(0) {}
 
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index e43379a..4c5521f1 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/extensions/tab_helper.h"
 
+#include "chrome/browser/extensions/activity_log.h"
 #include "chrome/browser/extensions/app_notify_channel_ui.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -54,6 +55,19 @@
 
 int TabHelper::kUserDataKey;
 
+TabHelper::ContentScriptObserver::ContentScriptObserver(TabHelper* tab_helper)
+    : tab_helper_(tab_helper) {
+  tab_helper_->AddContentScriptObserver(this);
+}
+
+TabHelper::ContentScriptObserver::ContentScriptObserver() : tab_helper_(NULL) {
+}
+
+TabHelper::ContentScriptObserver::~ContentScriptObserver() {
+  if (tab_helper_)
+    tab_helper_->RemoveContentScriptObserver(this);
+}
+
 TabHelper::TabHelper(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
       extension_app_(NULL),
@@ -72,18 +86,24 @@
       Profile::FromBrowserContext(web_contents->GetBrowserContext())));
   if (switch_utils::AreScriptBadgesEnabled()) {
     location_bar_controller_.reset(
-        new ScriptBadgeController(web_contents, &script_executor_));
+        new ScriptBadgeController(web_contents, &script_executor_, this));
   } else {
     location_bar_controller_.reset(
         new PageActionController(web_contents));
   }
+
+  // If more classes need to listen to global content script activity, then
+  // a separate routing class with an observer interface should be written.
+  AddContentScriptObserver(ActivityLog::GetInstance());
+
   registrar_.Add(this,
                  content::NOTIFICATION_LOAD_STOP,
                  content::Source<NavigationController>(
-                    &web_contents->GetController()));
+                     &web_contents->GetController()));
 }
 
 TabHelper::~TabHelper() {
+  RemoveContentScriptObserver(ActivityLog::GetInstance());
 }
 
 void TabHelper::CreateApplicationShortcuts() {
@@ -185,6 +205,8 @@
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
                         OnGetAppInstallState);
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
+    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
+                        OnContentScriptsExecuting)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
   return handled;
@@ -367,6 +389,17 @@
                                           web_contents()->GetRenderViewHost());
 }
 
+void TabHelper::OnContentScriptsExecuting(
+    const ContentScriptObserver::ExecutingScriptsMap& executing_scripts_map,
+    int32 on_page_id,
+    const GURL& on_url) {
+  FOR_EACH_OBSERVER(ContentScriptObserver, content_script_observers_,
+                    OnContentScriptsExecuting(web_contents(),
+                                              executing_scripts_map,
+                                              on_page_id,
+                                              on_url));
+}
+
 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
   if (extension_app_id.empty())
     return NULL;
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index 01800ad..9540029 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -31,7 +31,6 @@
 class Extension;
 class LocationBarController;
 class ScriptBadgeController;
-class ScriptExecutor;
 
 // Per-tab extension helper. Also handles non-extension apps.
 class TabHelper : public content::WebContentsObserver,
@@ -50,8 +49,41 @@
     UPDATE_SHORTCUT   // Update icon for app shortcut.
   };
 
+  // Observer base class for classes listening for content script messages
+  // from the renderer.
+  class ContentScriptObserver {
+   public:
+    // Map of extensions IDs to the executing script paths.
+    typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+    // Automatically observes and unobserves |tab_helper| on construction
+    // and destruction. |tab_helper| must outlive |this|.
+    explicit ContentScriptObserver(TabHelper* tab_helper);
+    ContentScriptObserver();
+
+    virtual void OnContentScriptsExecuting(
+        const content::WebContents* web_contents,
+        const ExecutingScriptsMap& executing_scripts_map,
+        int32 on_page_id,
+        const GURL& on_url) = 0;
+
+   protected:
+    virtual ~ContentScriptObserver();
+
+   private:
+    TabHelper* tab_helper_;
+  };
+
   virtual ~TabHelper();
 
+  void AddContentScriptObserver(ContentScriptObserver* observer) {
+    content_script_observers_.AddObserver(observer);
+  }
+
+  void RemoveContentScriptObserver(ContentScriptObserver* observer) {
+    content_script_observers_.RemoveObserver(observer);
+  }
+
   void CreateApplicationShortcuts();
   bool CanCreateApplicationShortcuts() const;
 
@@ -147,6 +179,10 @@
                             int return_route_id,
                             int callback_id);
   void OnRequest(const ExtensionHostMsg_Request_Params& params);
+  void OnContentScriptsExecuting(
+      const ContentScriptObserver::ExecutingScriptsMap& extension_ids,
+      int32 page_id,
+      const GURL& on_url);
 
   // App extensions related methods:
 
@@ -186,9 +222,9 @@
 
   // Data for app extensions ---------------------------------------------------
 
-  // Our observers. Declare at top so that it will outlive all other members,
-  // since they might add themselves as observers.
-  ObserverList<Observer> observers_;
+  // Our content script observers. Declare at top so that it will outlive all
+  // other members, since they might add themselves as observers.
+  ObserverList<ContentScriptObserver> content_script_observers_;
 
   // If non-null this tab is an app tab and this is the extension the tab was
   // created for.
diff --git a/chrome/browser/resources/extensions/extension_activity.css b/chrome/browser/resources/extensions/extension_activity.css
index 54ab2f0..fb40cdf4 100644
--- a/chrome/browser/resources/extensions/extension_activity.css
+++ b/chrome/browser/resources/extensions/extension_activity.css
@@ -11,6 +11,7 @@
 }
 
 .extension-activity-time,
-.extension-activity-label {
+.extension-activity-label,
+.extension-activity-message {
   -webkit-padding-end: 10px;
 }
diff --git a/chrome/browser/resources/extensions/extension_activity.html b/chrome/browser/resources/extensions/extension_activity.html
index 411cc69..5a659f4 100644
--- a/chrome/browser/resources/extensions/extension_activity.html
+++ b/chrome/browser/resources/extensions/extension_activity.html
@@ -41,6 +41,8 @@
         i18n-content="extensionActivityApiCall"></span>
     <span class="extension-activity-label-1"
         i18n-content="extensionActivityApiBlock"></span>
+    <span class="extension-activity-label-2"
+        i18n-content="extensionActivityContentScript"></span>
   </div>
 
   <div class="extension-activity-item">
diff --git a/chrome/browser/resources/extensions/extension_activity.js b/chrome/browser/resources/extensions/extension_activity.js
index e4eb85c..b0f8866b 100644
--- a/chrome/browser/resources/extensions/extension_activity.js
+++ b/chrome/browser/resources/extensions/extension_activity.js
@@ -25,6 +25,7 @@
   function handleExtensionActivity(result) {
     var template = $('template-collection');
 
+    // Clone the activity item template.
     var item =
         template.querySelector('.extension-activity-item').cloneNode(true);
     item.querySelector('.extension-activity-time').textContent =
@@ -32,8 +33,15 @@
     item.querySelector('.extension-activity-label').textContent =
         template.querySelector('.extension-activity-label-' + result.activity)
             .textContent;
-    item.querySelector('.extension-activity-message').textContent =
-        result.message;
+
+    // Clone the message node and then delete the empty template.
+    var msgNode = item.querySelector('.extension-activity-message');
+    for (var i = 0; i < result.messages.length; ++i) {
+      var newNode = msgNode.cloneNode(true);
+      newNode.textContent = result.messages[i];
+      item.appendChild(newNode);
+    }
+    item.removeChild(msgNode);
 
     $('extension-activity-list').appendChild(item);
   }
diff --git a/chrome/browser/ui/webui/extensions/extension_activity_ui.cc b/chrome/browser/ui/webui/extensions/extension_activity_ui.cc
index 93dd8227..589a29d 100644
--- a/chrome/browser/ui/webui/extensions/extension_activity_ui.cc
+++ b/chrome/browser/ui/webui/extensions/extension_activity_ui.cc
@@ -33,6 +33,8 @@
                              IDS_EXTENSION_ACTIVITY_API_CALL);
   source->AddLocalizedString("extensionActivityApiBlock",
                              IDS_EXTENSION_ACTIVITY_API_BLOCK);
+  source->AddLocalizedString("extensionActivityContentScript",
+                             IDS_EXTENSION_ACTIVITY_CONTENT_SCRIPT);
   source->set_use_json_js_format_v2();
   source->set_json_path("strings.js");
 
@@ -94,10 +96,14 @@
 void ExtensionActivityUI::OnExtensionActivity(
       const extensions::Extension* extension,
       extensions::ActivityLog::Activity activity,
-      const std::string& msg) {
+      const std::vector<std::string>& messages) {
+  scoped_ptr<ListValue> messages_list(new ListValue());
+  messages_list->AppendStrings(messages);
+
   DictionaryValue result;
   result.SetInteger("activity", activity);
-  result.SetString("message", msg);
+  result.Set("messages", messages_list.release());
+
   web_ui()->CallJavascriptFunction("extension_activity.handleExtensionActivity",
                                    result);
 }
diff --git a/chrome/browser/ui/webui/extensions/extension_activity_ui.h b/chrome/browser/ui/webui/extensions/extension_activity_ui.h
index a86cec8..f77ea424 100644
--- a/chrome/browser/ui/webui/extensions/extension_activity_ui.h
+++ b/chrome/browser/ui/webui/extensions/extension_activity_ui.h
@@ -22,9 +22,10 @@
   void HandleRequestExtensionData(const base::ListValue* args);
 
   // ActivityLog::Observer implementation.
-  virtual void OnExtensionActivity(const extensions::Extension* extension,
-                                   extensions::ActivityLog::Activity activity,
-                                   const std::string& msg) OVERRIDE;
+  virtual void OnExtensionActivity(
+      const extensions::Extension* extension,
+      extensions::ActivityLog::Activity activity,
+      const std::vector<std::string>& messages) OVERRIDE;
 
  private:
   const extensions::Extension* extension_;
diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h
index 9bed176..444c423 100644
--- a/chrome/common/extensions/extension_messages.h
+++ b/chrome/common/extensions/extension_messages.h
@@ -115,6 +115,9 @@
 // Substitution map for l10n messages.
 typedef std::map<std::string, std::string> SubstitutionMap;
 
+// Map of extensions IDs to the executing script paths.
+typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
 struct ExtensionMsg_Loaded_Params {
   ExtensionMsg_Loaded_Params();
   ~ExtensionMsg_Loaded_Params();
@@ -499,7 +502,7 @@
 // frame (e.g. if executing in an iframe this is the page ID of the parent,
 // unless the parent is an iframe... etc).
 IPC_MESSAGE_ROUTED3(ExtensionHostMsg_ContentScriptsExecuting,
-                    std::set<std::string> /* extensions that have scripts */,
+                    ExecutingScriptsMap,
                     int32 /* page_id of the _topmost_ frame */,
                     GURL /* url of the _topmost_ frame */)
 
diff --git a/chrome/renderer/extensions/user_script_slave.cc b/chrome/renderer/extensions/user_script_slave.cc
index c807516..7507ed1 100644
--- a/chrome/renderer/extensions/user_script_slave.cc
+++ b/chrome/renderer/extensions/user_script_slave.cc
@@ -262,7 +262,7 @@
   int num_css = 0;
   int num_scripts = 0;
 
-  std::set<std::string> extensions_executing_scripts;
+  ExecutingScriptsMap extensions_executing_scripts;
 
   for (size_t i = 0; i < scripts_.size(); ++i) {
     std::vector<WebScriptSource> sources;
@@ -332,7 +332,11 @@
           EXTENSION_GROUP_CONTENT_SCRIPTS);
       UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer.Elapsed());
 
-      extensions_executing_scripts.insert(extension->id());
+      for (std::vector<WebScriptSource>::const_iterator iter = sources.begin();
+           iter != sources.end(); ++iter) {
+        extensions_executing_scripts[extension->id()].insert(
+            GURL(iter->url).path());
+      }
     }
   }