Introduce ExtensionsService. Load extensions on startup from a directory in
the profile if a command-line flag is present.

Please carefully scrutinize the threading/ref-counting schenanigans.

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6403 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/browser.scons b/chrome/browser/browser.scons
index c29cc02..d6c6334 100644
--- a/chrome/browser/browser.scons
+++ b/chrome/browser/browser.scons
@@ -59,6 +59,7 @@
       'cross_site_request_manager.cc',
       'download/save_file.cc',
       'extensions/extension.cc',
+      'extensions/extensions_service.cc',
       'google_url_tracker.cc',
       'google_util.cc',
       'history/archived_database.cc',
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index 51140bc..fdfcfc7d 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -2173,6 +2173,14 @@
 				RelativePath=".\extensions\extension.h"

 				>

 			</File>

+			<File

+				RelativePath=".\extensions\extensions_service.cc"

+				>

+			</File>

+			<File

+				RelativePath=".\extensions\extensions_service.h"

+				>

+			</File>

 		</Filter>

 		<File

 			RelativePath=".\browser_trial.cc"

diff --git a/chrome/browser/browser_init.cc b/chrome/browser/browser_init.cc
index 0908689..b56c0b38 100644
--- a/chrome/browser/browser_init.cc
+++ b/chrome/browser/browser_init.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/browser_list.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/dom_ui/new_tab_ui.h"
+#include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/first_run.h"
 #include "chrome/browser/infobar_delegate.h"
 #include "chrome/browser/navigation_controller.h"
@@ -480,6 +481,11 @@
         base::EventRecorder::current()->StartPlayback(script_path);
     }
   }
+
+  // Start up the extensions service
+  if (parsed_command_line.HasSwitch(switches::kEnableExtensions))
+    profile->GetExtensionsService()->Init();
+
   return true;
 }
 
diff --git a/chrome/browser/extensions/extension.cc b/chrome/browser/extensions/extension.cc
index d4fbe9fe..b2db579 100644
--- a/chrome/browser/extensions/extension.cc
+++ b/chrome/browser/extensions/extension.cc
@@ -7,30 +7,35 @@
 #include "base/logging.h"
 #include "base/string_util.h"
 
-const std::wstring Extension::kFormatVersionKey(L"format_version");
-const std::wstring Extension::kIdKey(L"id");
-const std::wstring Extension::kNameKey(L"name");
-const std::wstring Extension::kDescriptionKey(L"description");
-const std::wstring Extension::kContentScriptsKey(L"content_scripts");
+const FilePath::CharType* Extension::kManifestFilename =
+    FILE_PATH_LITERAL("manifest");
 
-const std::wstring Extension::kInvalidFormatVersionError(
+const wchar_t* Extension::kFormatVersionKey = L"format_version";
+const wchar_t* Extension::kIdKey = L"id";
+const wchar_t* Extension::kNameKey = L"name";
+const wchar_t* Extension::kDescriptionKey = L"description";
+const wchar_t* Extension::kContentScriptsKey = L"content_scripts";
+
+const wchar_t* Extension::kInvalidManifestError =
+    L"Manifest is missing or invalid.";
+const wchar_t* Extension::kInvalidFormatVersionError =
     StringPrintf(L"Required key '%ls' is missing or invalid",
-                 kFormatVersionKey.c_str()));
-const std::wstring Extension::kInvalidIdError(
+                 kFormatVersionKey).c_str();
+const wchar_t* Extension::kInvalidIdError =
     StringPrintf(L"Required key '%ls' is missing or invalid.",
-                 kIdKey.c_str()));
-const std::wstring Extension::kInvalidNameError(
+                 kIdKey).c_str();
+const wchar_t* Extension::kInvalidNameError =
     StringPrintf(L"Required key '%ls' is missing or has invalid type.",
-                 kNameKey.c_str()));
-const std::wstring Extension::kInvalidDescriptionError(
+                 kNameKey).c_str();
+const wchar_t* Extension::kInvalidDescriptionError =
     StringPrintf(L"Invalid type for '%ls' key.",
-                 kDescriptionKey.c_str()));
-const std::wstring Extension::kInvalidContentScriptsListError(
+                 kDescriptionKey).c_str();
+const wchar_t* Extension::kInvalidContentScriptsListError =
     StringPrintf(L"Invalid type for '%ls' key.",
-                 kContentScriptsKey.c_str()));
-const std::wstring Extension::kInvalidContentScriptError(
+                 kContentScriptsKey).c_str();
+const wchar_t* Extension::kInvalidContentScriptError =
     StringPrintf(L"Invalid type for %ls at index ",
-                 kContentScriptsKey.c_str()));
+                 kContentScriptsKey).c_str();
 
 bool Extension::InitFromValue(const DictionaryValue& source,
                               std::wstring* error) {
diff --git a/chrome/browser/extensions/extension.h b/chrome/browser/extensions/extension.h
index 16260e00..94127ac 100644
--- a/chrome/browser/extensions/extension.h
+++ b/chrome/browser/extensions/extension.h
@@ -2,12 +2,13 @@
 // 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_H__
-#define CHROME_BROWSER_EXTENSIONS_EXTENSION_H__
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_H_
 
 #include <string>
 #include <vector>
 
+#include "base/file_path.h"
 #include "base/string16.h"
 #include "base/values.h"
 
@@ -19,20 +20,24 @@
   // The format for extension manifests that this code understands.
   static const int kExpectedFormatVersion = 1;
 
+  // The name of the manifest inside an extension.
+  static const FilePath::CharType* kManifestFilename;
+
   // Keys used in JSON representation of extensions.
-  static const std::wstring kFormatVersionKey;
-  static const std::wstring kIdKey;
-  static const std::wstring kNameKey;
-  static const std::wstring kDescriptionKey;
-  static const std::wstring kContentScriptsKey;
+  static const wchar_t* kFormatVersionKey;
+  static const wchar_t* kIdKey;
+  static const wchar_t* kNameKey;
+  static const wchar_t* kDescriptionKey;
+  static const wchar_t* kContentScriptsKey;
 
   // Error messages returned from InitFromValue().
-  static const std::wstring kInvalidFormatVersionError;
-  static const std::wstring kInvalidIdError;
-  static const std::wstring kInvalidNameError;
-  static const std::wstring kInvalidDescriptionError;
-  static const std::wstring kInvalidContentScriptsListError;
-  static const std::wstring kInvalidContentScriptError;
+  static const wchar_t* kInvalidFormatVersionError;
+  static const wchar_t* kInvalidManifestError;
+  static const wchar_t* kInvalidIdError;
+  static const wchar_t* kInvalidNameError;
+  static const wchar_t* kInvalidDescriptionError;
+  static const wchar_t* kInvalidContentScriptsListError;
+  static const wchar_t* kInvalidContentScriptError;
 
   // A human-readable ID for the extension. The convention is to use something
   // like 'com.example.myextension', but this is not currently enforced. An
@@ -68,4 +73,4 @@
   DISALLOW_COPY_AND_ASSIGN(Extension);
 };
 
-#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_H__
+#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_H_
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
new file mode 100644
index 0000000..67d508eb
--- /dev/null
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2006-2008 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/extensions_service.h"
+
+#include "base/file_util.h"
+#include "base/values.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/common/json_value_serializer.h"
+
+// ExtensionsService
+
+const FilePath::CharType* ExtensionsService::kInstallDirectoryName =
+    FILE_PATH_LITERAL("Extensions");
+
+ExtensionsService::ExtensionsService(const FilePath& profile_directory)
+    : message_loop_(MessageLoop::current()),
+      backend_(new ExtensionsServiceBackend),
+      install_directory_(profile_directory.Append(kInstallDirectoryName)) {
+}
+
+ExtensionsService::~ExtensionsService() {
+  for (ExtensionList::iterator iter = extensions_.begin();
+       iter != extensions_.end(); ++iter) {
+    delete *iter;
+  }
+}
+
+bool ExtensionsService::Init() {
+  // TODO(aa): This message loop should probably come from a backend
+  // interface, similar to how the message loop for the frontend comes
+  // from the frontend interface.
+  g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
+      NewRunnableMethod(backend_.get(),
+          &ExtensionsServiceBackend::LoadExtensionsFromDirectory,
+          install_directory_,
+          scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
+  // TODO(aa): Load extensions from other registered directories.
+
+  return true;
+}
+
+MessageLoop* ExtensionsService::GetMessageLoop() {
+  return message_loop_;
+}
+
+void ExtensionsService::OnExtensionsLoadedFromDirectory(
+    ExtensionList* extensions) {
+  extensions_.assign(extensions->begin(), extensions->end());
+  delete extensions;
+
+  // TODO(aa): Notify extensions are loaded.
+}
+
+void ExtensionsService::OnExtensionLoadError(const std::wstring& error) {
+  // TODO(aa): Print the error message out somewhere better. Ideally we would
+  // use the JavaScript console I think, but that is complicated since these
+  // errors are not related to any particular page.
+  LOG(WARNING) << "Error loading extension: " << error;
+}
+
+
+// ExtensionsServicesBackend
+
+bool ExtensionsServiceBackend::LoadExtensionsFromDirectory(
+    const FilePath& path,
+    scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
+  // Find all child directories in the install directory and load their
+  // manifests. Post errors and results to the frontend.
+  scoped_ptr<ExtensionList> extensions(new ExtensionList);
+  file_util::FileEnumerator enumerator(path.ToWStringHack(),
+                                       false, // not recursive
+                                       file_util::FileEnumerator::DIRECTORIES);
+  for (std::wstring child_path = enumerator.Next(); !child_path.empty();
+       child_path = enumerator.Next()) {
+     FilePath manifest_path = FilePath::FromWStringHack(child_path).Append(
+         Extension::kManifestFilename);
+    if (!file_util::PathExists(manifest_path)) {
+      ReportExtensionLoadError(frontend.get(), 
+                               Extension::kInvalidManifestError);
+      continue;
+    }
+
+    JSONFileValueSerializer serializer(manifest_path.ToWStringHack());
+    Value* root = NULL;
+    if (!serializer.Deserialize(&root)) {
+      ReportExtensionLoadError(frontend.get(), 
+                               Extension::kInvalidManifestError);
+      continue;
+    }
+
+    if (!root->IsType(Value::TYPE_DICTIONARY)) {
+      ReportExtensionLoadError(frontend.get(), 
+                               Extension::kInvalidManifestError);
+      continue;
+    }
+
+    scoped_ptr<Extension> extension(new Extension());
+    std::wstring error;
+    if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root),
+                                  &error)) {
+      ReportExtensionLoadError(frontend.get(), 
+                               Extension::kInvalidManifestError);
+      continue;
+    }
+
+    extensions->push_back(extension.release());
+  }
+
+  ReportExtensionsLoaded(frontend.get(), extensions.release());
+  return true;
+}
+
+void ExtensionsServiceBackend::ReportExtensionLoadError(
+    ExtensionsServiceFrontendInterface *frontend, const std::wstring &error) {
+  frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
+      frontend, &ExtensionsServiceFrontendInterface::OnExtensionLoadError,
+      error));
+}
+
+void ExtensionsServiceBackend::ReportExtensionsLoaded(
+    ExtensionsServiceFrontendInterface *frontend, ExtensionList* extensions) {
+  frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
+      frontend,
+      &ExtensionsServiceFrontendInterface::OnExtensionsLoadedFromDirectory,
+      extensions));
+}
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
new file mode 100644
index 0000000..17f25b76
--- /dev/null
+++ b/chrome/browser/extensions/extensions_service.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2006-2008 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_EXTENSIONS_SERVICE_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSIONS_SERVICE_H_
+
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/message_loop.h"
+#include "base/ref_counted.h"
+#include "base/task.h"
+#include "chrome/browser/extensions/extension.h"
+
+typedef std::vector<Extension*> ExtensionList;
+class ExtensionsServiceBackend;
+
+// Interface for the frontend to implement. Typically, this will be
+// ExtensionsService, but it can also be a test harness.
+class ExtensionsServiceFrontendInterface
+    : public base::RefCountedThreadSafe<ExtensionsServiceFrontendInterface> {
+ public:
+  virtual ~ExtensionsServiceFrontendInterface(){}
+
+  // The message loop to invoke the frontend's methods on.
+  virtual MessageLoop* GetMessageLoop() = 0;
+
+  // Called when loading an extension fails.
+  virtual void OnExtensionLoadError(const std::wstring& message) = 0;
+
+  // Called with results from LoadExtensionsFromDirectory(). The frontend
+  // takes ownership of the list.
+  virtual void OnExtensionsLoadedFromDirectory(ExtensionList* extensions) = 0;
+};
+
+
+// Manages installed and running Chromium extensions.
+class ExtensionsService : public ExtensionsServiceFrontendInterface {
+ public:
+  ExtensionsService(const FilePath& profile_directory);
+  ~ExtensionsService();
+
+  // Gets the list of currently installed extensions.
+  const ExtensionList* extensions() const {
+    return &extensions_;
+  }
+
+  // Initialize and start all installed extensions.
+  bool Init();
+
+  // ExtensionsServiceFrontendInterface
+  virtual MessageLoop* GetMessageLoop();
+  virtual void OnExtensionLoadError(const std::wstring& message);
+  virtual void OnExtensionsLoadedFromDirectory(ExtensionList* extensions);
+
+ private:
+  // The name of the directory inside the profile where extensions are
+  // installed to.
+  static const FilePath::CharType* kInstallDirectoryName;
+
+  // The message loop for the thread the ExtensionsService is running on.
+  MessageLoop* message_loop_;
+
+  // The backend that will do IO on behalf of this instance.
+  scoped_refptr<ExtensionsServiceBackend> backend_;
+
+  // The current list of installed extensions.
+  ExtensionList extensions_;
+
+  // The full path to the directory where extensions are installed.
+  FilePath install_directory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionsService);
+};
+
+// Implements IO for the ExtensionsService.
+// TODO(aa): Extract an interface out of this for testing the frontend, once the
+// frontend has significant logic to test.
+class ExtensionsServiceBackend
+    : public base::RefCountedThreadSafe<ExtensionsServiceBackend> {
+ public:
+  ExtensionsServiceBackend(){};
+
+  // Loads extensions from a directory. The extensions are assumed to be
+  // unpacked in directories that are direct children of the specified path.
+  // Errors are reported through OnExtensionLoadError(). On completion,
+  // OnExtensionsLoadedFromDirectory() is called with any successfully loaded
+  // extensions.
+  bool LoadExtensionsFromDirectory(
+      const FilePath &path,
+      scoped_refptr<ExtensionsServiceFrontendInterface> frontend);
+
+ private:
+  // Notify a frontend that there was an error loading an extension.
+  void ReportExtensionLoadError(ExtensionsServiceFrontendInterface* frontend,
+                                const std::wstring& error);
+
+  // Notify a frontend that extensions were loaded.
+  void ReportExtensionsLoaded(ExtensionsServiceFrontendInterface* frontend,
+                              ExtensionList* extensions);
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionsServiceBackend);
+};
+
+#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSIONS_SERVICE_H_
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
new file mode 100644
index 0000000..22e891d
--- /dev/null
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2006-2008 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 <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/json_value_serializer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class ExtensionsServiceTest : public testing::Test {
+};
+
+
+// A mock implementation of ExtensionsServiceFrontendInterface for testing the
+// backend.
+class ExtensionsServiceTestFrontend
+    : public ExtensionsServiceFrontendInterface {
+ public:
+  std::vector<std::wstring>* errors() {
+    return &errors_;
+  }
+
+  ExtensionList* extensions() {
+    return extensions_.get();
+  }
+
+  // ExtensionsServiceFrontendInterface
+  virtual MessageLoop* GetMessageLoop() {
+    return &message_loop_;
+  }
+
+  virtual void OnExtensionLoadError(const std::wstring& message) {
+    errors_.push_back(message);
+  }
+
+  virtual void OnExtensionsLoadedFromDirectory(ExtensionList* extensions) {
+    extensions_.reset(extensions);
+  }
+
+ private:
+  MessageLoop message_loop_;
+  scoped_ptr<ExtensionList> extensions_;
+  std::vector<std::wstring> errors_;
+};
+
+
+// Test loading extensions from the profile directory.
+TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectory) {
+#if defined(OS_WIN)
+  std::wstring extensions_dir;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_dir));
+  FilePath manifest_path = FilePath::FromWStringHack(extensions_dir).Append(
+      FILE_PATH_LITERAL("extensions"));
+
+  scoped_refptr<ExtensionsServiceBackend> backend(new ExtensionsServiceBackend);
+  scoped_refptr<ExtensionsServiceTestFrontend> frontend(
+      new ExtensionsServiceTestFrontend);
+
+  std::vector<Extension*> extensions;
+  EXPECT_TRUE(backend->LoadExtensionsFromDirectory(manifest_path,
+      scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get())));
+  frontend->GetMessageLoop()->RunAllPending();
+
+  // Note: There can be more errors if there are extra directories, like .svn
+  // directories.
+  EXPECT_TRUE(frontend->errors()->size() >= 2u);
+  EXPECT_EQ(Extension::kInvalidManifestError, frontend->errors()->at(0));
+  EXPECT_EQ(Extension::kInvalidManifestError, frontend->errors()->at(1));
+  EXPECT_EQ(2u, frontend->extensions()->size());
+
+  EXPECT_EQ(std::wstring(L"com.google.myextension1"),
+            frontend->extensions()->at(0)->id());
+  EXPECT_EQ(std::wstring(L"My extension 1"),
+            frontend->extensions()->at(0)->name());
+  EXPECT_EQ(std::wstring(L"The first extension that I made."),
+            frontend->extensions()->at(0)->description());
+  EXPECT_EQ(2u, frontend->extensions()->at(0)->content_scripts().size());
+  EXPECT_EQ(std::wstring(L"script1.user.js"),
+            frontend->extensions()->at(0)->content_scripts().at(0));
+  EXPECT_EQ(std::wstring(L"script2.user.js"),
+            frontend->extensions()->at(0)->content_scripts().at(1));
+
+  EXPECT_EQ(std::wstring(L"com.google.myextension2"),
+            frontend->extensions()->at(1)->id());
+  EXPECT_EQ(std::wstring(L"My extension 2"),
+            frontend->extensions()->at(1)->name());
+  EXPECT_EQ(std::wstring(L""),
+            frontend->extensions()->at(1)->description());
+  EXPECT_EQ(0u, frontend->extensions()->at(1)->content_scripts().size());
+#endif
+};
diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc
index fb3508a4..76f2f16 100644
--- a/chrome/browser/profile.cc
+++ b/chrome/browser/profile.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/browser_list.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/greasemonkey_master.h"
 #include "chrome/browser/history/history.h"
 #include "chrome/browser/navigation_controller.h"
@@ -384,6 +385,10 @@
     return profile_->GetVisitedLinkMaster();
   }
 
+  virtual ExtensionsService* GetExtensionsService() {
+    return profile_->GetExtensionsService();
+  }
+
   virtual GreasemonkeyMaster* GetGreasemonkeyMaster() {
     return profile_->GetGreasemonkeyMaster();
   }
@@ -547,6 +552,7 @@
 ProfileImpl::ProfileImpl(const std::wstring& path)
     : path_(path),
       off_the_record_(false),
+      extensions_service_(new ExtensionsService(FilePath(path))),
       history_service_created_(false),
       created_web_data_service_(false),
       created_download_manager_(false),
@@ -688,6 +694,10 @@
   return visited_link_master_.get();
 }
 
+ExtensionsService* ProfileImpl::GetExtensionsService() {
+  return extensions_service_.get();
+}
+
 GreasemonkeyMaster* ProfileImpl::GetGreasemonkeyMaster() {
   if (!greasemonkey_master_.get()) {
     std::wstring script_dir_str;
diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h
index cb3c2ac..51a5c32 100644
--- a/chrome/browser/profile.h
+++ b/chrome/browser/profile.h
@@ -24,6 +24,7 @@
 
 class BookmarkModel;
 class DownloadManager;
+class ExtensionsService;
 class GreasemonkeyMaster;
 class HistoryService;
 class NavigationController;
@@ -102,6 +103,10 @@
   // that this method is called.
   virtual VisitedLinkMaster* GetVisitedLinkMaster() = 0;
 
+  // Retrieves a pointer to the ExtensionsService associated with this
+  // profile. The ExtensionsService is created at startup.
+  virtual ExtensionsService* GetExtensionsService() = 0;
+
   // Retrieves a pointer to the GreasemonkeyMaster associated with this
   // profile.  The GreasemonkeyMaster is lazily created the first time
   // that this method is called.
@@ -243,6 +248,7 @@
   virtual Profile* GetOriginalProfile();
   virtual VisitedLinkMaster* GetVisitedLinkMaster();
   virtual GreasemonkeyMaster* GetGreasemonkeyMaster();
+  virtual ExtensionsService* GetExtensionsService();
   virtual HistoryService* GetHistoryService(ServiceAccessType sat);
   virtual WebDataService* GetWebDataService(ServiceAccessType sat);
   virtual PrefService* GetPrefs();
@@ -303,6 +309,7 @@
   std::wstring path_;
   bool off_the_record_;
   scoped_ptr<VisitedLinkMaster> visited_link_master_;
+  scoped_refptr<ExtensionsService> extensions_service_;
   scoped_refptr<GreasemonkeyMaster> greasemonkey_master_;
   scoped_ptr<PrefService> prefs_;
   scoped_ptr<TemplateURLFetcher> template_url_fetcher_;