This is the first part of the PageAction implementation. More work is required, but this is a good checkpoint.

Design doc: http://dev.chromium.org/developers/design-documents/extensions/page-actions-api

This checkin only covers Tab scoped page actions (not type "permanent"). It works end to end (if you have an extension that supplies the page action info -- I created an RSS page action that links to Google Reader).

Please note that TabIndex is hard coded to 0 until the extension system can provide the tab id to the extensions (which I understand is in progress). This means that page action(s) only show up for the first tab in the tabstrip. :)

BUG=None
TEST=There is a unit test for the API, but apart from that it is not possible to test this manually without writing an extension that adds a PageAction. My RSS page action is  not ready to be checked in but I can provide it if there is interest in a sneak preview during review/QA.
Review URL: http://codereview.chromium.org/99253

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15105 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
index 6d50e66bd..ac794764 100644
--- a/chrome/browser/browser.cc
+++ b/chrome/browser/browser.cc
@@ -2370,6 +2370,9 @@
     if (flags & TabContents::INVALIDATE_FEEDLIST)
       window()->GetLocationBar()->UpdateFeedIcon();
 
+    if (flags & TabContents::INVALIDATE_PAGE_ACTIONS)
+      window()->GetLocationBar()->UpdatePageActions();
+
     // Updating the URL happens synchronously in ScheduleUIUpdate.
 
     if (flags & TabContents::INVALIDATE_LOAD && GetStatusBubble())
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index 88150e2..2afce4c 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -1974,6 +1974,14 @@
 				>
 			</File>
 			<File
+				RelativePath=".\extensions\extension_page_actions_module.cc"
+				>
+			</File>
+			<File
+				RelativePath=".\extensions\extension_page_actions_module.h"
+				>
+			</File>
+			<File
 				RelativePath=".\extensions\extension_protocols.cc"
 				>
 			</File>
diff --git a/chrome/browser/cocoa/location_bar_view_mac.h b/chrome/browser/cocoa/location_bar_view_mac.h
index 90cb2a6..7de0d95 100644
--- a/chrome/browser/cocoa/location_bar_view_mac.h
+++ b/chrome/browser/cocoa/location_bar_view_mac.h
@@ -39,6 +39,7 @@
   virtual void FocusLocation();
   virtual void FocusSearch() { NOTIMPLEMENTED(); }
   virtual void UpdateFeedIcon() { /* http://crbug.com/8832 */ }
+  virtual void UpdatePageActions() { NOTIMPLEMENTED(); }
   virtual void SaveStateToContents(TabContents* contents);
 
   virtual void OnAutocompleteAccept(const GURL& url,
diff --git a/chrome/browser/extensions/extension.cc b/chrome/browser/extensions/extension.cc
index 6b2bb3e..35b169e 100644
--- a/chrome/browser/extensions/extension.cc
+++ b/chrome/browser/extensions/extension.cc
@@ -1,10 +1,11 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009 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/extension.h"
 
 #include "base/file_path.h"
+#include "base/file_util.h"
 #include "base/logging.h"
 #include "base/string_util.h"
 #include "net/base/net_util.h"
@@ -18,21 +19,28 @@
 const wchar_t* Extension::kContentScriptsKey = L"content_scripts";
 const wchar_t* Extension::kCssKey = L"css";
 const wchar_t* Extension::kDescriptionKey = L"description";
+const wchar_t* Extension::kIconPathKey = L"icon";
 const wchar_t* Extension::kIdKey = L"id";
 const wchar_t* Extension::kJsKey = L"js";
 const wchar_t* Extension::kMatchesKey = L"matches";
 const wchar_t* Extension::kNameKey = L"name";
+const wchar_t* Extension::kPageActionsKey = L"page_actions";
 const wchar_t* Extension::kPermissionsKey = L"permissions";
 const wchar_t* Extension::kPluginsDirKey = L"plugins_dir";
 const wchar_t* Extension::kBackgroundKey = L"background";
 const wchar_t* Extension::kRunAtKey = L"run_at";
 const wchar_t* Extension::kThemeKey = L"theme";
 const wchar_t* Extension::kToolstripsKey = L"toolstrips";
+const wchar_t* Extension::kTooltipKey = L"tooltip";
+const wchar_t* Extension::kTypeKey = L"type";
 const wchar_t* Extension::kVersionKey = L"version";
 const wchar_t* Extension::kZipHashKey = L"zip_hash";
 
 const char* Extension::kRunAtDocumentStartValue = "document_start";
 const char* Extension::kRunAtDocumentEndValue = "document_end";
+const char* Extension::kPageActionTypeTab = "tab";
+const char* Extension::kPageActionTypePermanent = "permanent";
+
 
 // Extension-related error messages. Some of these are simple patterns, where a
 // '*' is replaced at runtime with a specific value. This is used instead of
@@ -65,6 +73,16 @@
     "Required value 'content_scripts[*].matches' is missing or invalid.";
 const char* Extension::kInvalidNameError =
     "Required value 'name' is missing or invalid.";
+const char* Extension::kInvalidPageActionError =
+    "Invalid value for 'page_actions[*]'.";
+const char* Extension::kInvalidPageActionsListError =
+    "Invalid value for 'page_actions'.";
+const char* Extension::kInvalidPageActionIconPathError =
+    "Invalid value for 'page_actions[*].icon'.";
+const char* Extension::kInvalidPageActionTooltipError =
+    "Invalid value for 'page_actions[*].tooltip'.";
+const char* Extension::kInvalidPageActionTypeValueError =
+    "Invalid value for 'page_actions[*].type', expected 'tab' or 'permanent'.";
 const char* Extension::kInvalidPermissionsError =
     "Required value 'permissions' is missing or invalid.";
 const char* Extension::kInvalidPermissionCountWarning =
@@ -90,6 +108,8 @@
     "Required key 'zip_hash' is missing or invalid.";
 const char* Extension::kMissingFileError =
     "At least one js or css file is required for 'content_scripts[*]'.";
+const char* Extension::kMissingPageActionIcon =
+    "Unable to find 'page_actions[*].icon'";
 
 const size_t Extension::kIdSize = 20;  // SHA1 (160 bits) == 20 bytes
 
@@ -101,11 +121,18 @@
       name_(rhs.name_),
       description_(rhs.description_),
       content_scripts_(rhs.content_scripts_),
+      page_actions_(rhs.page_actions_),
       plugins_dir_(rhs.plugins_dir_),
       zip_hash_(rhs.zip_hash_),
       theme_paths_(rhs.theme_paths_) {
 }
 
+Extension::~Extension() {
+  for (PageActionMap::iterator i = page_actions_.begin();
+       i != page_actions_.end(); ++i)
+    delete i->second;
+}
+
 const std::string Extension::VersionString() const {
   return version_->GetString();
 }
@@ -130,6 +157,14 @@
   return FilePath();
 }
 
+bool Extension::UpdatePageAction(std::string id, int tab_id, GURL url) {
+  if (page_actions_.find(id) == page_actions_.end())
+    return false;
+
+  page_actions_[id]->SetActiveTabIdAndUrl(tab_id, url);
+  return true;
+}
+
 // static
 FilePath Extension::GetResourcePath(const FilePath& extension_path,
                                     const std::string& relative_path) {
@@ -323,6 +358,74 @@
   return true;
 }
 
+// Helper method that loads a PageAction object from a dictionary in the
+// page_action list of the manifest.
+bool Extension::LoadPageActionHelper(const DictionaryValue* page_action,
+                                     int definition_index, std::string* error,
+                                     PageAction* result) {
+  result->set_extension_id(id());
+
+  // Read the page action |icon|.
+  std::string icon;
+  if (!page_action->GetString(kIconPathKey, &icon)) {
+    *error = FormatErrorMessage(kInvalidPageActionIconPathError,
+                                IntToString(definition_index));
+    return false;
+  }
+  FilePath icon_path = path_.AppendASCII(icon);
+  if (!file_util::PathExists(icon_path)) {
+    *error = FormatErrorMessage(kMissingPageActionIcon,
+                                IntToString(definition_index));
+    return false;
+  }
+  result->set_icon_path(icon_path);
+
+  // Read the page action |id|.
+  std::string id;
+  if (!page_action->GetString(kIdKey, &id)) {
+    *error = FormatErrorMessage(kInvalidIdError, IntToString(definition_index));
+    return false;
+  }
+  result->set_id(id);
+
+  // Read the page action |name|.
+  std::string name;
+  if (!page_action->GetString(kNameKey, &name)) {
+    *error = FormatErrorMessage(kInvalidNameError,
+                                IntToString(definition_index));
+    return false;
+  }
+  result->set_name(name);
+
+  // Read the page action |tooltip|.
+  std::string tooltip;
+  if (!page_action->GetString(kTooltipKey, &tooltip)) {
+    *error = FormatErrorMessage(kInvalidPageActionTooltipError,
+                                IntToString(definition_index));
+    return false;
+  }
+  result->set_tooltip(tooltip);
+
+  // Read the page action |type|. It is optional and set to permanent if
+  // missing.
+  std::string type;
+  if (!page_action->GetString(kTypeKey, &type)) {
+    result->set_type(PageAction::PERMANENT);
+  } else if (!LowerCaseEqualsASCII(type, kPageActionTypeTab) &&
+             !LowerCaseEqualsASCII(type, kPageActionTypePermanent)) {
+    *error = FormatErrorMessage(kInvalidPageActionTypeValueError,
+                                IntToString(definition_index));
+    return false;
+  } else {
+    if (LowerCaseEqualsASCII(type, kPageActionTypeTab))
+      result->set_type(PageAction::TAB);
+    else
+      result->set_type(PageAction::PERMANENT);
+  }
+
+  return true;
+}
+
 bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
                               std::string* error) {
   // Initialize id.
@@ -474,6 +577,31 @@
     }
   }
 
+  // Initialize page actions (optional).
+  if (source.HasKey(kPageActionsKey)) {
+    ListValue* list_value;
+    if (!source.GetList(kPageActionsKey, &list_value)) {
+      *error = kInvalidPageActionsListError;
+      return false;
+    }
+
+    for (size_t i = 0; i < list_value->GetSize(); ++i) {
+      DictionaryValue* page_action_value;
+      if (!list_value->GetDictionary(i, &page_action_value)) {
+        *error = FormatErrorMessage(kInvalidPageActionError,
+                                    IntToString(i));
+        return false;
+      }
+
+      PageAction* page_action = new PageAction();
+      if (!LoadPageActionHelper(page_action_value, i, error, page_action)) {
+        delete page_action;
+        return false;  // Failed to parse page action definition.
+      }
+      page_actions_[page_action->id()] = page_action;
+    }
+  }
+
   // Initialize the permissions (optional).
   if (source.HasKey(kPermissionsKey)) {
     ListValue* hosts = NULL;
diff --git a/chrome/browser/extensions/extension.h b/chrome/browser/extensions/extension.h
index 95f04bb..bf6269444 100644
--- a/chrome/browser/extensions/extension.h
+++ b/chrome/browser/extensions/extension.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009 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.
 
@@ -16,6 +16,7 @@
 #include "base/version.h"
 #include "chrome/browser/extensions/user_script_master.h"
 #include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/page_action.h"
 #include "googleurl/src/gurl.h"
 
 // Represents a Chromium extension.
@@ -24,6 +25,7 @@
   Extension() {}
   explicit Extension(const FilePath& path);
   explicit Extension(const Extension& path);
+  virtual ~Extension();
 
   // The name of the manifest inside an extension.
   static const char kManifestFilename[];
@@ -32,22 +34,28 @@
   static const wchar_t* kContentScriptsKey;
   static const wchar_t* kCssKey;
   static const wchar_t* kDescriptionKey;
+  static const wchar_t* kIconPathKey;
   static const wchar_t* kIdKey;
   static const wchar_t* kJsKey;
   static const wchar_t* kMatchesKey;
   static const wchar_t* kNameKey;
+  static const wchar_t* kPageActionsKey;
   static const wchar_t* kPermissionsKey;
   static const wchar_t* kPluginsDirKey;
   static const wchar_t* kBackgroundKey;
   static const wchar_t* kRunAtKey;
   static const wchar_t* kThemeKey;
   static const wchar_t* kToolstripsKey;
+  static const wchar_t* kTooltipKey;
+  static const wchar_t* kTypeKey;
   static const wchar_t* kVersionKey;
   static const wchar_t* kZipHashKey;
 
   // Some values expected in manifests.
   static const char* kRunAtDocumentStartValue;
   static const char* kRunAtDocumentEndValue;
+  static const char* kPageActionTypeTab;
+  static const char* kPageActionTypePermanent;
 
   // Error messages returned from InitFromValue().
   static const char* kInvalidContentScriptError;
@@ -69,12 +77,18 @@
   static const char* kInvalidToolstripError;
   static const char* kInvalidToolstripsError;
   static const char* kInvalidVersionError;
+  static const char* kInvalidPageActionError;
+  static const char* kInvalidPageActionsListError;
+  static const char* kInvalidPageActionIconPathError;
+  static const char* kInvalidPageActionTooltipError;
+  static const char* kInvalidPageActionTypeValueError;
   static const char* kInvalidPermissionsError;
   static const char* kInvalidPermissionCountWarning;
   static const char* kInvalidPermissionError;
   static const char* kInvalidPermissionSchemeError;
   static const char* kInvalidZipHashError;
   static const char* kMissingFileError;
+  static const char* kMissingPageActionIcon;
 
   // The number of bytes in a legal id.
   static const size_t kIdSize;
@@ -114,6 +128,10 @@
   // as providing a theme.
   FilePath GetThemeResourcePath(const int resource_id);
 
+  // Update the status of the page action with |id| in tab |tab_id| and set the
+  // current page url to |url|.
+  bool UpdatePageAction(std::string id, int tab_id, GURL url);
+
   const FilePath& path() const { return path_; }
   const GURL& url() const { return extension_url_; }
   const std::string& id() const { return id_; }
@@ -123,6 +141,7 @@
   const std::string& name() const { return name_; }
   const std::string& description() const { return description_; }
   const UserScriptList& content_scripts() const { return content_scripts_; }
+  const PageActionMap& page_actions() const { return page_actions_; }
   const FilePath& plugins_dir() const { return plugins_dir_; }
   const GURL& background_url() const { return background_url_; }
   const std::vector<std::string>& toolstrips() const { return toolstrips_; }
@@ -136,6 +155,14 @@
                             int definition_index,
                             std::string* error,
                             UserScript* result);
+
+  // Helper method that loads a PageAction object from a
+  // dictionary in the page_action list of the manifest.
+  bool LoadPageActionHelper(const DictionaryValue* page_action,
+                            int definition_index,
+                            std::string* error,
+                            PageAction* result);
+
   // The absolute path to the directory the extension is stored in.
   FilePath path_;
 
@@ -162,6 +189,9 @@
   // Paths to the content scripts the extension contains.
   UserScriptList content_scripts_;
 
+  // A list of page actions.
+  PageActionMap page_actions_;
+
   // Optional absolute path to the directory of NPAPI plugins that the extension
   // contains.
   FilePath plugins_dir_;
diff --git a/chrome/browser/extensions/extension_browser_event_router.cc b/chrome/browser/extensions/extension_browser_event_router.cc
index c05e793..fb3d354 100644
--- a/chrome/browser/extensions/extension_browser_event_router.cc
+++ b/chrome/browser/extensions/extension_browser_event_router.cc
@@ -13,14 +13,15 @@
 #include "chrome/browser/extensions/extension_tabs_module.h"
 #include "chrome/common/notification_service.h"
 
-const char* kOnWindowCreated = "window-created";
-const char* kOnWindowRemoved = "window-removed";
+const char* kOnPageActionExecuted = "page-action-executed";
 const char* kOnTabCreated = "tab-created";
 const char* kOnTabMoved = "tab-moved";
 const char* kOnTabSelectionChanged = "tab-selection-changed";
 const char* kOnTabAttached = "tab-attached";
 const char* kOnTabDetached = "tab-detached";
 const char* kOnTabRemoved = "tab-removed";
+const char* kOnWindowCreated = "window-created";
+const char* kOnWindowRemoved = "window-removed";
 
 ExtensionBrowserEventRouter* ExtensionBrowserEventRouter::GetInstance() {
   return Singleton<ExtensionBrowserEventRouter>::get();
@@ -200,3 +201,24 @@
                                                bool loading_only) { }
 
 void ExtensionBrowserEventRouter::TabStripEmpty() { }
+
+void ExtensionBrowserEventRouter::PageActionExecuted(Profile *profile,
+                                                     std::string page_action_id,
+                                                     int tab_id,
+                                                     std::string url) {
+  ListValue args;
+  DictionaryValue *object_args = new DictionaryValue();
+  object_args->Set(L"pageActionId", Value::CreateStringValue(page_action_id));
+
+  DictionaryValue *data = new DictionaryValue();
+  data->Set(L"tabId", Value::CreateIntegerValue(tab_id));
+  data->Set(L"tabUrl", Value::CreateStringValue(url));
+  object_args->Set(L"data", data);
+
+  args.Append(object_args);
+
+  std::string json_args;
+  JSONWriter::Write(&args, false, &json_args);
+
+  DispatchEvent(profile, kOnPageActionExecuted, json_args);
+}
diff --git a/chrome/browser/extensions/extension_browser_event_router.h b/chrome/browser/extensions/extension_browser_event_router.h
index 29ec3983..51099fd 100644
--- a/chrome/browser/extensions/extension_browser_event_router.h
+++ b/chrome/browser/extensions/extension_browser_event_router.h
@@ -22,13 +22,13 @@
 class ExtensionBrowserEventRouter : public TabStripModelObserver,
                                     public NotificationObserver {
  public:
-  // Get Browser-Global instance
+  // Get Browser-Global instance.
   static ExtensionBrowserEventRouter* GetInstance();
 
   // Must be called once. Subsequent calls have no effect.
   void Init();
 
-  // TabStripModelObserver
+  // TabStripModelObserver.
   void TabInsertedAt(TabContents* contents, int index, bool foreground);
   void TabClosingAt(TabContents* contents, int index);
   void TabDetachedAt(TabContents* contents, int index);
@@ -40,7 +40,13 @@
   void TabChangedAt(TabContents* contents, int index, bool loading_only);
   void TabStripEmpty();
 
-  // NotificationObserver
+  // PageActions.
+  void PageActionExecuted(Profile *profile,
+                          std::string page_action_id,
+                          int tab_id,
+                          std::string url);
+
+  // NotificationObserver.
   void Observe(NotificationType type,
                const NotificationSource& source,
                const NotificationDetails& details);
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 0d45d84e..8843ee8 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -11,6 +11,7 @@
 #include "base/values.h"
 #include "chrome/browser/extensions/extension_bookmarks_module.h"
 #include "chrome/browser/extensions/extension_function.h"
+#include "chrome/browser/extensions/extension_page_actions_module.h"
 #include "chrome/browser/extensions/extension_tabs_module.h"
 #include "chrome/browser/renderer_host/render_process_host.h"
 #include "chrome/browser/renderer_host/render_view_host.h"
@@ -50,7 +51,7 @@
 FactoryRegistry::FactoryRegistry() {
   // Register all functions here.
 
-  // Tabs
+  // Tabs.
   factories_["GetWindows"] = &NewExtensionFunction<GetWindowsFunction>;
   factories_["CreateWindow"] = &NewExtensionFunction<CreateWindowFunction>;
   factories_["RemoveWindow"] = &NewExtensionFunction<RemoveWindowFunction>;
@@ -62,7 +63,11 @@
   factories_["MoveTab"] = &NewExtensionFunction<MoveTabFunction>;
   factories_["RemoveTab"] = &NewExtensionFunction<RemoveTabFunction>;
 
-  // Bookmarks
+  // Page Actions.
+  factories_["EnablePageAction"] =
+      &NewExtensionFunction<EnablePageActionFunction>;
+
+  // Bookmarks.
   factories_["GetBookmarks"] = &NewExtensionFunction<GetBookmarksFunction>;
   factories_["GetBookmarkChildren"] =
       &NewExtensionFunction<GetBookmarkChildrenFunction>;
diff --git a/chrome/browser/extensions/extension_page_actions_module.cc b/chrome/browser/extensions/extension_page_actions_module.cc
new file mode 100644
index 0000000..edf08dd
--- /dev/null
+++ b/chrome/browser/extensions/extension_page_actions_module.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2009 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/extension_page_actions_module.h"
+
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/extensions/extension.h"
+#include "chrome/browser/extensions/extension_tabs_module.h"
+#include "chrome/browser/extensions/extensions_service.h"
+
+bool EnablePageActionFunction::RunImpl() {
+  EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_LIST));
+  const ListValue* args = static_cast<const ListValue*>(args_);
+
+  std::string page_action_id;
+  EXTENSION_FUNCTION_VALIDATE(args->GetString(0, &page_action_id));
+  DictionaryValue* action;
+  EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(1, &action));
+
+  int tab_id;
+  EXTENSION_FUNCTION_VALIDATE(action->GetInteger(L"tabId", &tab_id));
+  std::string url;
+  EXTENSION_FUNCTION_VALIDATE(action->GetString(L"url", &url));
+
+  Browser* browser = BrowserList::GetLastActive();
+  if (!browser)
+    return false;
+
+  // HACK: We need to figure out the tab index from the tab_id (pending).
+  // For now we only support page actions in the first tab in the strip (tab 0).
+  int tab_index = 0;
+
+  TabStripModel* tab_strip = browser->tabstrip_model();
+  TabContents* contents = tab_strip->GetTabContentsAt(tab_index);
+
+  // Not needed when we stop hard-coding the tab index.
+  tab_id = ExtensionTabUtil::GetTabId(contents);
+
+  // Find our extension.
+  Extension* extension = NULL;
+  if (profile()->GetExtensionsService()) {
+    const ExtensionList* extensions =
+        profile()->GetExtensionsService()->extensions();
+    for (ExtensionList::const_iterator iter = extensions->begin();
+        iter != extensions->end(); ++iter) {
+      if ((*iter)->id() == extension_id()) {
+        extension = (*iter);
+        break;  // Found our extension.
+      }
+    }
+  }
+
+  if (!extension ||
+      !extension->UpdatePageAction(page_action_id, tab_id, GURL(url)))
+    return false;
+
+  // Broadcast notifications when the UI should be updated.
+  contents->NotifyNavigationStateChanged(TabContents::INVALIDATE_PAGE_ACTIONS);
+
+  return true;
+}
diff --git a/chrome/browser/extensions/extension_page_actions_module.h b/chrome/browser/extensions/extension_page_actions_module.h
new file mode 100644
index 0000000..b6e9d87
--- /dev/null
+++ b/chrome/browser/extensions/extension_page_actions_module.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_PAGE_ACTIONS_MODULE_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_PAGE_ACTIONS_MODULE_H_
+
+#include "chrome/browser/extensions/extension_function.h"
+
+class EnablePageActionFunction : public SyncExtensionFunction {
+  virtual bool RunImpl();
+};
+
+#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_PAGE_ACTIONS_MODULE_H_
diff --git a/chrome/browser/gtk/location_bar_view_gtk.cc b/chrome/browser/gtk/location_bar_view_gtk.cc
index 4308c3b..11e4ac1 100644
--- a/chrome/browser/gtk/location_bar_view_gtk.cc
+++ b/chrome/browser/gtk/location_bar_view_gtk.cc
@@ -75,7 +75,7 @@
   gtk_widget_set_app_paintable(alignment_.get(), TRUE);
   // Have GTK double buffer around the expose signal.
   gtk_widget_set_double_buffered(alignment_.get(), TRUE);
-  g_signal_connect(alignment_.get(), "expose-event", 
+  g_signal_connect(alignment_.get(), "expose-event",
                    G_CALLBACK(&HandleExposeThunk), this);
 
   gtk_container_add(GTK_CONTAINER(alignment_.get()),
@@ -189,6 +189,10 @@
     NOTIMPLEMENTED();
 }
 
+void LocationBarViewGtk::UpdatePageActions() {
+  NOTIMPLEMENTED();
+}
+
 void LocationBarViewGtk::SaveStateToContents(TabContents* contents) {
   NOTIMPLEMENTED();
 }
diff --git a/chrome/browser/gtk/location_bar_view_gtk.h b/chrome/browser/gtk/location_bar_view_gtk.h
index bf7a8f1..22a7c9c 100644
--- a/chrome/browser/gtk/location_bar_view_gtk.h
+++ b/chrome/browser/gtk/location_bar_view_gtk.h
@@ -70,6 +70,7 @@
   virtual void FocusLocation();
   virtual void FocusSearch();
   virtual void UpdateFeedIcon();
+  virtual void UpdatePageActions();
   virtual void SaveStateToContents(TabContents* contents);
 
   // Translation between a security level and the background color.  Both the
diff --git a/chrome/browser/location_bar.h b/chrome/browser/location_bar.h
index a7b0c0d..a1f4898 100644
--- a/chrome/browser/location_bar.h
+++ b/chrome/browser/location_bar.h
@@ -50,6 +50,9 @@
   // Update the state of the feed icon.
   virtual void UpdateFeedIcon() = 0;
 
+  // Update the state of the page actions.
+  virtual void UpdatePageActions() = 0;
+
   // Saves the state of the location bar to the specified TabContents, so that
   // it can be restored later. (Done when switching tabs).
   virtual void SaveStateToContents(TabContents* contents) = 0;
diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h
index 181aafb..4ce03c5 100644
--- a/chrome/browser/tab_contents/tab_contents.h
+++ b/chrome/browser/tab_contents/tab_contents.h
@@ -97,7 +97,7 @@
     INVALIDATE_FAVICON = 4,    // The favicon has changed.
     INVALIDATE_LOAD = 8,       // The loading state has changed.
     INVALIDATE_FEEDLIST = 16,  // The Atom/RSS feed has changed.
-
+    INVALIDATE_PAGE_ACTIONS = 32, // Page action icons have changed.
     // Helper for forcing a refresh.
     INVALIDATE_EVERYTHING = 0xFFFFFFFF
   };
diff --git a/chrome/browser/views/location_bar_view.cc b/chrome/browser/views/location_bar_view.cc
index 3311a52..62c7a8d 100644
--- a/chrome/browser/views/location_bar_view.cc
+++ b/chrome/browser/views/location_bar_view.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009 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.
 
@@ -12,6 +12,9 @@
 #include "chrome/browser/browser_list.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/command_updater.h"
+#include "chrome/browser/extensions/extension_browser_event_router.h"
+#include "chrome/browser/extensions/extension_tabs_module.h"
+#include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/profile.h"
 #include "chrome/browser/search_engines/template_url.h"
 #include "chrome/browser/search_engines/template_url_model.h"
@@ -102,6 +105,10 @@
   }
 }
 
+LocationBarView::~LocationBarView() {
+  DeletePageActionViews();
+}
+
 bool LocationBarView::IsInitialized() const {
   return location_entry_view_ != NULL;
 }
@@ -178,6 +185,7 @@
 void LocationBarView::Update(const TabContents* tab_for_state_restoring) {
   SetSecurityIcon(model_->GetIcon());
   SetRssIconVisibility(model_->GetFeedList().get());
+  RefreshPageActionViews();
   std::wstring info_text, info_tooltip;
   SkColor text_color;
   model_->GetInfoText(&info_text, &text_color, &info_tooltip);
@@ -193,6 +201,13 @@
   SchedulePaint();
 }
 
+void LocationBarView::UpdatePageActions() {
+  RefreshPageActionViews();
+
+  Layout();
+  SchedulePaint();
+}
+
 void LocationBarView::Focus() {
   ::SetFocus(location_entry_->m_hWnd);
 }
@@ -346,10 +361,18 @@
 
   int entry_width = width() - (kEntryPadding * 2);
 
+  gfx::Size page_action_size;
+  for (size_t i = 0; i < page_action_image_views_.size(); i++) {
+    if (page_action_image_views_[i]->IsVisible()) {
+      page_action_size = page_action_image_views_[i]->GetPreferredSize();
+      entry_width -= (page_action_size.width() + kInnerPadding) *
+                     page_action_image_views_.size();
+    }
+  }
   gfx::Size rss_image_size;
   if (rss_image_view_.IsVisible()) {
     rss_image_size = rss_image_view_.GetPreferredSize();
-    entry_width -= rss_image_size.width();
+    entry_width -= rss_image_size.width() + kInnerPadding;
   }
   gfx::Size security_image_size;
   if (security_image_view_.IsVisible()) {
@@ -383,6 +406,19 @@
   }
   const int info_label_width = info_label_size.width() ?
       info_label_size.width() + kInnerPadding : 0;
+
+  int offset = width() - kEntryPadding - info_label_width -
+      security_image_size.width();
+
+  for (size_t i = 0; i < page_action_image_views_.size(); i++) {
+    if (page_action_image_views_[i]->IsVisible()) {
+      page_action_size = page_action_image_views_[i]->GetPreferredSize();
+      offset -= page_action_size.width();
+      page_action_image_views_[i]->SetBounds(offset, location_y,
+                                             page_action_size.width(),
+                                             location_height);
+    }
+  }
   if (rss_image_view_.IsVisible()) {
     rss_image_view_.SetBounds(width() - kEntryPadding -
                                   info_label_width -
@@ -529,6 +565,64 @@
   rss_image_view_.SetVisible(false);
 }
 
+void LocationBarView::DeletePageActionViews() {
+  if (!page_action_image_views_.empty()) {
+    for (size_t i = 0; i < page_action_image_views_.size(); ++i)
+      RemoveChildView(page_action_image_views_[i]);
+    STLDeleteContainerPointers(page_action_image_views_.begin(),
+                               page_action_image_views_.end());
+    page_action_image_views_.clear();
+  }
+}
+
+std::vector<PageAction*> LocationBarView::GetPageActions() {
+  std::vector<PageAction*> result;
+
+  // Query the extension system to see how many page actions we have.
+  // TODO(finnur): Sort the page icons in some meaningful way.
+  const ExtensionList* extensions =
+      profile_->GetExtensionsService()->extensions();
+  for (ExtensionList::const_iterator iter = extensions->begin();
+       iter != extensions->end(); ++iter) {
+    const PageActionMap& page_actions = (*iter)->page_actions();
+    for (PageActionMap::const_iterator i(page_actions.begin());
+         i != page_actions.end(); ++i) {
+      result.push_back(i->second);
+    }
+  }
+
+  return result;
+}
+
+void LocationBarView::RefreshPageActionViews() {
+  std::vector<PageAction*> page_actions = GetPageActions();
+
+  // On startup we sometimes haven't loaded any extensions. This makes sure
+  // we catch up when the extensions (and any page actions) load.
+  if (page_actions.size() != page_action_image_views_.size()) {
+    DeletePageActionViews();  // Delete the old views (if any).
+
+    page_action_image_views_.resize(page_actions.size());
+
+    for (size_t i = 0; i < page_actions.size(); ++i) {
+      page_action_image_views_[i] =
+          new PageActionImageView(this, profile_, page_actions[i]);
+      page_action_image_views_[i]->SetVisible(false);
+      page_action_image_views_[i]->SetParentOwned(false);
+      AddChildView(page_action_image_views_[i]);
+    }
+  }
+
+  TabContents* contents = delegate_->GetTabContents();
+  if (!page_action_image_views_.empty() && contents) {
+    int tab_id = ExtensionTabUtil::GetTabId(contents);
+    GURL url = GURL(model_->GetText());
+
+    for (size_t i = 0; i < page_action_image_views_.size(); i++)
+      page_action_image_views_[i]->UpdateVisibility(tab_id, url);
+  }
+}
+
 void LocationBarView::SetInfoText(const std::wstring& text,
                                   SkColor text_color,
                                   const std::wstring& tooltip_text) {
@@ -840,7 +934,7 @@
   LocationBarView::LocationBarImageView* image_view_;
   bool cancelled_;
 
-  DISALLOW_EVIL_CONSTRUCTORS(ShowInfoBubbleTask);
+  DISALLOW_COPY_AND_ASSIGN(ShowInfoBubbleTask);
 };
 
 LocationBarView::ShowInfoBubbleTask::ShowInfoBubbleTask(
@@ -1078,6 +1172,67 @@
   ShowInfoBubbleImpl(text, text_color);
 }
 
+// PageActionImageView----------------------------------------------------------
+
+LocationBarView::PageActionImageView::PageActionImageView(
+    LocationBarView* owner,
+    Profile* profile,
+    const PageAction* page_action)
+  : owner_(owner),
+    profile_(profile),
+    page_action_(page_action),
+    LocationBarImageView() {
+  // The first thing we do is try to set the image from the PageAction icon.
+  if (page_action->icon_path().empty()) {
+    NOTREACHED();
+    return;  // Need icon path to continue.
+  }
+
+  IconManager* im = g_browser_process->icon_manager();
+
+  SkBitmap* icon = im->LookupIcon(page_action->icon_path(),
+                                  IconLoader::SMALL);
+  if (icon) {
+    ImageView::SetImage(icon);
+  } else {
+    // Ask the Icon Manager to load the icon (happens on the File thread).
+    IconManager::Handle h = im->LoadIcon(page_action->icon_path(),
+        IconLoader::SMALL, &icon_consumer_,
+        NewCallback(this, &LocationBarView::PageActionImageView::OnIconLoaded));
+  }
+}
+
+LocationBarView::PageActionImageView::~PageActionImageView() {
+  icon_consumer_.CancelAllRequests();
+}
+
+bool LocationBarView::PageActionImageView::OnMousePressed(
+    const views::MouseEvent& event) {
+  // Our PageAction icon was clicked on, notify proper authorities.
+  ExtensionBrowserEventRouter::GetInstance()->PageActionExecuted(
+      profile_, page_action_->id(), current_tab_id_, current_url_.spec());
+  return true;
+}
+
+void LocationBarView::PageActionImageView::ShowInfoBubble() {
+  SkColor text_color = SK_ColorBLACK;
+  ShowInfoBubbleImpl(ASCIIToWide(page_action_->tooltip()), text_color);
+}
+
+void LocationBarView::PageActionImageView::UpdateVisibility(
+    int tab_id, GURL url) {
+  current_tab_id_ = tab_id;
+  current_url_ = url;
+
+  SetVisible(page_action_->IsActive(current_tab_id_, current_url_));
+}
+
+void LocationBarView::PageActionImageView::OnIconLoaded(
+    IconManager::Handle handle, SkBitmap* icon) {
+  ImageView::SetImage(icon);
+  owner_->UpdatePageActions();
+}
+
 bool LocationBarView::OverrideAccelerator(
     const views::Accelerator& accelerator)  {
   return location_entry_->OverrideAccelerator(accelerator);
diff --git a/chrome/browser/views/location_bar_view.h b/chrome/browser/views/location_bar_view.h
index 7251a85..58be56d2 100644
--- a/chrome/browser/views/location_bar_view.h
+++ b/chrome/browser/views/location_bar_view.h
@@ -1,15 +1,17 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2009 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.
 
-#ifndef CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H__
-#define CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H__
+#ifndef CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H_
+#define CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H_
 
 #include <string>
+#include <vector>
 
 #include "base/gfx/rect.h"
 #include "chrome/browser/autocomplete/autocomplete_edit.h"
 #include "chrome/browser/autocomplete/autocomplete_edit_view_win.h"
+#include "chrome/browser/icon_manager.h"
 #include "chrome/browser/location_bar.h"
 #include "chrome/browser/tab_contents/tab_contents.h"
 #include "chrome/browser/toolbar_model.h"
@@ -23,6 +25,7 @@
 class AutocompletePopupPositioner;
 class CommandUpdater;
 class GURL;
+class PageAction;
 class Profile;
 
 /////////////////////////////////////////////////////////////////////////////
@@ -54,7 +57,7 @@
                   Delegate* delegate,
                   bool popup_window_mode,
                   AutocompletePopupPositioner* popup_positioner);
-  virtual ~LocationBarView() { }
+  virtual ~LocationBarView();
 
   void Init();
 
@@ -124,6 +127,7 @@
   virtual void FocusLocation();
   virtual void FocusSearch();
   virtual void UpdateFeedIcon();
+  virtual void UpdatePageActions();
   virtual void SaveStateToContents(TabContents* contents);
 
   static const int kVertMargin;
@@ -181,7 +185,7 @@
 
     Profile* profile_;
 
-    DISALLOW_EVIL_CONSTRUCTORS(SelectedKeywordView);
+    DISALLOW_COPY_AND_ASSIGN(SelectedKeywordView);
   };
 
   // KeywordHintView is used to display a hint to the user when the selected
@@ -221,7 +225,7 @@
 
     Profile* profile_;
 
-    DISALLOW_EVIL_CONSTRUCTORS(KeywordHintView);
+    DISALLOW_COPY_AND_ASSIGN(KeywordHintView);
   };
 
 
@@ -304,7 +308,7 @@
 
     ToolbarModel* model_;
 
-    DISALLOW_EVIL_CONSTRUCTORS(SecurityImageView);
+    DISALLOW_COPY_AND_ASSIGN(SecurityImageView);
   };
 
   // RssImageView is used to display the RSS icon when the page has a feed that
@@ -331,6 +335,51 @@
     DISALLOW_COPY_AND_ASSIGN(RssImageView);
   };
 
+  // PageActionImageView is used to display the icon for a given PageAction
+  // and notify the extension when the icon is clicked.
+  class PageActionImageView : public LocationBarImageView {
+  public:
+    PageActionImageView(
+        LocationBarView* owner, Profile* profile,
+        const PageAction* page_action);
+    virtual ~PageActionImageView();
+
+    // Overridden from view for the mouse hovering.
+    virtual bool OnMousePressed(const views::MouseEvent& event);
+
+    // Overridden from LocationBarImageView.
+    virtual void ShowInfoBubble();
+
+    // Called to notify the PageAction that it should determine whether to be
+    // visible or hidden.
+    void UpdateVisibility(int tab_id, GURL url);
+
+    // Called when the IconManager has loaded our icon.
+    void OnIconLoaded(IconManager::Handle handle, SkBitmap* icon);
+
+  private:
+    // The location bar view that owns us.
+    LocationBarView* owner_;
+
+    // The current profile (not owned by us).
+    Profile* profile_;
+
+    // The PageAction that this view represents. The PageAction is not owned by
+    // us, it resides in the extension of this particular profile.
+    const PageAction* page_action_;
+
+    // The tab id we are currently showing the icon for.
+    int current_tab_id_;
+
+    // The URL we are currently showing the icon for.
+    GURL current_url_;
+
+    // For canceling an in progress icon request.
+    CancelableRequestConsumerT<int, 0> icon_consumer_;
+
+    DISALLOW_COPY_AND_ASSIGN(PageActionImageView);
+  };
+
   // Both Layout and OnChanged call into this. This updates the contents
   // of the 3 views: selected_keyword, keyword_hint and type_search_view. If
   // force_layout is true, or one of these views has changed in such a way as
@@ -371,6 +420,17 @@
   // Sets the RSS icon visibility.
   void SetRssIconVisibility(FeedList* feeds);
 
+  // Delete all page action views that we have created.
+  void DeletePageActionViews();
+
+  // Retrieves a vector of all page actions, irrespective of which
+  // extension they belong to.
+  std::vector<PageAction*> LocationBarView::GetPageActions();
+
+  // Update the views for the Page Actions, to reflect state changes for
+  // PageActions.
+  void RefreshPageActionViews();
+
   // Sets the text that should be displayed in the info label and its associated
   // tooltip text.  Call with an empty string if the info label should be
   // hidden.
@@ -439,6 +499,9 @@
   // The view that shows the RSS icon when the page has an RSS feed.
   RssImageView rss_image_view_;
 
+  // The page action icon views.
+  std::vector<PageActionImageView*> page_action_image_views_;
+
   // A label displayed after the lock icon to show some extra information.
   views::Label info_label_;
 
@@ -453,4 +516,4 @@
   AutocompletePopupPositioner* popup_positioner_;
 };
 
-#endif // CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H__
+#endif  // CHROME_BROWSER_VIEWS_LOCATION_BAR_VIEW_H_