diff --git a/base/string_util.cc b/base/string_util.cc
index a62b905227ec58c7474629243bb9744309958074..11ac623dc63d754b307c3edec0667de563725c65 100644
--- a/base/string_util.cc
+++ b/base/string_util.cc
@@ -851,6 +851,11 @@ bool EndsWithT(const STR& str, const STR& search, bool case_sensitive) {
   }
 }
 
+bool EndsWith(const std::string& str, const std::string& search,
+              bool case_sensitive) {
+  return EndsWithT(str, search, case_sensitive);
+}
+
 bool EndsWith(const std::wstring& str, const std::wstring& search,
               bool case_sensitive) {
   return EndsWithT(str, search, case_sensitive);
diff --git a/base/string_util.h b/base/string_util.h
index 1c58e91990a8a4e8a250d4f72af406de54427469..89caf4f29818f200a43de7fc32b2b03ff32d1397 100644
--- a/base/string_util.h
+++ b/base/string_util.h
@@ -291,6 +291,9 @@ bool StartsWith(const string16& str,
                 bool case_sensitive);
 
 // Returns true if str ends with search, or false otherwise.
+bool EndsWith(const std::string& str,
+              const std::string& search,
+              bool case_sensitive);
 bool EndsWith(const std::wstring& str,
               const std::wstring& search,
               bool case_sensitive);
diff --git a/chrome/browser/download/download_manager.cc b/chrome/browser/download/download_manager.cc
index eef8b04f9b1c925655e686733756f436663bba4c..bb0a280aae7701a68bdfd9a27b327f79aee07ae1 100644
--- a/chrome/browser/download/download_manager.cc
+++ b/chrome/browser/download/download_manager.cc
@@ -34,6 +34,7 @@
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/user_script.h"
 #include "chrome/common/notification_service.h"
 #include "chrome/common/notification_type.h"
 #include "chrome/common/platform_util.h"
@@ -1243,14 +1244,25 @@ void DownloadManager::OpenChromeExtension(const FilePath& full_path,
       nservice->Notify(NotificationType::EXTENSION_READY_FOR_INSTALL,
                        Source<DownloadManager>(this),
                        NotificationService::NoDetails());
-    CrxInstaller::Start(full_path,
-                        service->install_directory(),
-                        Extension::INTERNAL,
-                        "",  // no expected id
-                        true,  // please delete crx on completion
-                        true,  // privilege increase allowed
-                        service,
-                        new ExtensionInstallUI(profile_));
+    if (UserScript::HasUserScriptFileExtension(full_path)) {
+      CrxInstaller::InstallUserScript(
+          full_path,
+          download_url,
+          service->install_directory(),
+          true,  // please delete crx on completion
+          service,
+          new ExtensionInstallUI(profile_));
+    } else {
+      CrxInstaller::Start(
+          full_path,
+          service->install_directory(),
+          Extension::INTERNAL,
+          "",  // no expected id
+          true,  // please delete crx on completion
+          true,  // privilege increase allowed
+          service,
+          new ExtensionInstallUI(profile_));
+    }
   }
 }
 
@@ -1453,11 +1465,23 @@ void DownloadManager::GenerateSafeFilename(const std::string& mime_type,
 }
 
 bool DownloadManager::IsExtensionInstall(const DownloadItem* item) {
-  return item->mime_type() == Extension::kMimeType && !item->save_as();
+  if (item->save_as())
+    return false;
+
+  if (UserScript::HasUserScriptFileExtension(item->original_name()))
+    return true;
+
+  return item->mime_type() == Extension::kMimeType;
 }
 
 bool DownloadManager::IsExtensionInstall(const DownloadCreateInfo* info) {
-  return info->mime_type == Extension::kMimeType && !info->save_as;
+  if (info->save_as)
+    return false;
+
+  if (UserScript::HasUserScriptFileExtension(info->path))
+    return true;
+
+  return info->mime_type == Extension::kMimeType;
 }
 
 // Operations posted to us from the history service ----------------------------
diff --git a/chrome/browser/extensions/convert_user_script.cc b/chrome/browser/extensions/convert_user_script.cc
new file mode 100644
index 0000000000000000000000000000000000000000..70de6d90532f8ca7001a8b8253793d182acc6f58
--- /dev/null
+++ b/chrome/browser/extensions/convert_user_script.cc
@@ -0,0 +1,138 @@
+// 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/convert_user_script.h"
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/scoped_temp_dir.h"
+#include "base/sha2.h"
+#include "base/string_util.h"
+#include "chrome/browser/extensions/user_script_master.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/user_script.h"
+#include "chrome/common/json_value_serializer.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/base64.h"
+
+namespace keys = extension_manifest_keys;
+
+Extension* ConvertUserScriptToExtension(const FilePath& user_script_path,
+                                        const GURL& original_url,
+                                        std::string* error){
+  std::string content;
+  if (!file_util::ReadFileToString(user_script_path, &content)) {
+    *error = "Could not read source file: " +
+        WideToASCII(user_script_path.ToWStringHack());
+    return NULL;
+  }
+
+  UserScript script;
+  if (!UserScriptMaster::ScriptReloader::ParseMetadataHeader(content,
+                                                             &script)) {
+    *error = "Invalid script header.";
+    return NULL;
+  }
+
+  ScopedTempDir temp_dir;
+  if (!temp_dir.CreateUniqueTempDir()) {
+    *error = "Could not create temporary directory.";
+    return NULL;
+  }
+
+  // Create the manifest
+  scoped_ptr<DictionaryValue> root(new DictionaryValue);
+  std::string script_name;
+  if (!script.name().empty() && !script.name_space().empty())
+    script_name = script.name_space() + "/" + script.name();
+  else
+    script_name = original_url.spec();
+
+  // Create the public key.
+  // User scripts are not signed, but the public key for an extension doubles as
+  // its unique identity, and we need one of those. A user script's unique
+  // identity is its namespace+name, so we hash that to create a public key.
+  // There will be no corresponding private key, which means user scripts cannot
+  // be auto-updated, or claimed in the gallery.
+  char raw[base::SHA256_LENGTH] = {0};
+  std::string key;
+  base::SHA256HashString(script_name, raw, base::SHA256_LENGTH);
+  net::Base64Encode(std::string(raw, base::SHA256_LENGTH), &key);
+
+  // The script may not have a name field, but we need one for an extension. If
+  // it is missing, use the filename of the original URL.
+  if (!script.name().empty())
+    root->SetString(keys::kName, script.name());
+  else
+    root->SetString(keys::kName, original_url.ExtractFileName());
+
+  root->SetString(keys::kDescription, script.description());
+  root->SetString(keys::kVersion, "1.0");
+  root->SetString(keys::kPublicKey, key);
+  root->SetBoolean(keys::kConvertedFromUserScript, true);
+
+  ListValue* js_files = new ListValue();
+  js_files->Append(Value::CreateStringValue("script.js"));
+
+  // If the script provides its own match patterns, we use those. Otherwise, we
+  // generate some using the include globs.
+  ListValue* matches = new ListValue();
+  if (!script.url_patterns().empty()) {
+    for (size_t i = 0; i < script.url_patterns().size(); ++i) {
+      matches->Append(Value::CreateStringValue(
+          script.url_patterns()[i].GetAsString()));
+    }
+  } else {
+    // TODO(aa): Derive tighter matches where possible.
+    matches->Append(Value::CreateStringValue("http://*/*"));
+    matches->Append(Value::CreateStringValue("https://*/*"));
+  }
+
+  ListValue* includes = new ListValue();
+  for (size_t i = 0; i < script.globs().size(); ++i)
+    includes->Append(Value::CreateStringValue(script.globs().at(i)));
+
+  ListValue* excludes = new ListValue();
+  for (size_t i = 0; i < script.exclude_globs().size(); ++i)
+    excludes->Append(Value::CreateStringValue(script.exclude_globs().at(i)));
+
+  DictionaryValue* content_script = new DictionaryValue();
+  content_script->Set(keys::kMatches, matches);
+  content_script->Set(keys::kIncludeGlobs, includes);
+  content_script->Set(keys::kExcludeGlobs, excludes);
+  content_script->Set(keys::kJs, js_files);
+
+  ListValue* content_scripts = new ListValue();
+  content_scripts->Append(content_script);
+
+  root->Set(keys::kContentScripts, content_scripts);
+
+  FilePath manifest_path = temp_dir.path().AppendASCII(
+      Extension::kManifestFilename);
+  JSONFileValueSerializer serializer(manifest_path);
+  if (!serializer.Serialize(*root)) {
+    *error = "Could not write JSON.";
+    return NULL;
+  }
+
+  // Write the script file.
+  if (!file_util::CopyFile(user_script_path,
+                           temp_dir.path().AppendASCII("script.js"))) {
+    *error = "Could not copy script file.";
+    return NULL;
+  }
+
+  scoped_ptr<Extension> extension(new Extension(temp_dir.path()));
+  if (!extension->InitFromValue(*root, false, error)) {
+    NOTREACHED() << "Could not init extension " << *error;
+    return NULL;
+  }
+
+  temp_dir.Take();  // The caller takes ownership of the directory.
+  return extension.release();
+}
diff --git a/chrome/browser/extensions/convert_user_script.h b/chrome/browser/extensions/convert_user_script.h
new file mode 100644
index 0000000000000000000000000000000000000000..ebc9ee23f6197173f4b231390a6d2af790384a17
--- /dev/null
+++ b/chrome/browser/extensions/convert_user_script.h
@@ -0,0 +1,23 @@
+// 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_CONVERT_USER_SCRIPT_H_
+#define CHROME_BROWSER_EXTENSIONS_CONVERT_USER_SCRIPT_H_
+
+#include <string>
+
+class Extension;
+class FilePath;
+class GURL;
+
+// Wraps the specified user script in an extension. The extension is created
+// unpacked in the system temp dir. Returns a valid extension that the caller
+// should take ownership on success, or NULL and |error| on failure.
+//
+// NOTE: This function does file IO and should not be called on the UI thread.
+Extension* ConvertUserScriptToExtension(const FilePath& user_script,
+                                        const GURL& original_url,
+                                        std::string* error);
+
+#endif  // CHROME_BROWSER_EXTENSIONS_CONVERT_USER_SCRIPT_H_
diff --git a/chrome/browser/extensions/convert_user_script_unittest.cc b/chrome/browser/extensions/convert_user_script_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b7aea54ffb49e01562f36bc900861f044f7c643c
--- /dev/null
+++ b/chrome/browser/extensions/convert_user_script_unittest.cc
@@ -0,0 +1,88 @@
+// 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 <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/extensions/convert_user_script.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(ExtensionFromUserScript, Basic) {
+  FilePath test_file;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_file));
+  test_file = test_file.AppendASCII("extensions")
+                       .AppendASCII("user_script_basic.user.js");
+
+  std::string error;
+  scoped_ptr<Extension> extension(ConvertUserScriptToExtension(
+      test_file, GURL("http://www.google.com/foo"), &error));
+
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ("", error);
+
+  // Validate generated extension metadata.
+  EXPECT_EQ("My user script", extension->name());
+  EXPECT_EQ("1.0", extension->VersionString());
+  EXPECT_EQ("Does totally awesome stuff.", extension->description());
+  EXPECT_EQ("IhCFCg9PMQTAcJdc9ytUP99WME+4yh6aMnM1uupkovo=",
+            extension->public_key());
+
+  ASSERT_EQ(1u, extension->content_scripts().size());
+  const UserScript& script = extension->content_scripts()[0];
+  ASSERT_EQ(2u, script.globs().size());
+  EXPECT_EQ("http://www.google.com/*", script.globs().at(0));
+  EXPECT_EQ("http://www.yahoo.com/*", script.globs().at(1));
+  ASSERT_EQ(1u, script.exclude_globs().size());
+  EXPECT_EQ("*foo*", script.exclude_globs().at(0));
+  ASSERT_EQ(1u, script.url_patterns().size());
+  EXPECT_EQ("http://www.google.com/*", script.url_patterns()[0].GetAsString());
+
+  // Make sure the files actually exist on disk.
+  EXPECT_TRUE(file_util::PathExists(
+      extension->path().Append(script.js_scripts()[0].relative_path())));
+  EXPECT_TRUE(file_util::PathExists(
+      extension->path().AppendASCII(Extension::kManifestFilename)));
+}
+
+TEST(ExtensionFromUserScript, NoMetdata) {
+  FilePath test_file;
+  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_file));
+  test_file = test_file.AppendASCII("extensions")
+                       .AppendASCII("user_script_no_metadata.user.js");
+
+  std::string error;
+  scoped_ptr<Extension> extension(ConvertUserScriptToExtension(
+      test_file, GURL("http://www.google.com/foo/bar.user.js?monkey"), &error));
+
+  ASSERT_TRUE(extension.get());
+  EXPECT_EQ("", error);
+
+  // Validate generated extension metadata.
+  EXPECT_EQ("bar.user.js", extension->name());
+  EXPECT_EQ("1.0", extension->VersionString());
+  EXPECT_EQ("", extension->description());
+  EXPECT_EQ("k1WxKx54hX6tfl5gQaXD/m4d9QUMwRdXWM4RW+QkWcY=",
+            extension->public_key());
+
+  ASSERT_EQ(1u, extension->content_scripts().size());
+  const UserScript& script = extension->content_scripts()[0];
+  ASSERT_EQ(1u, script.globs().size());
+  EXPECT_EQ("*", script.globs()[0]);
+  EXPECT_EQ(0u, script.exclude_globs().size());
+  ASSERT_EQ(2u, script.url_patterns().size());
+  EXPECT_EQ("http://*/*", script.url_patterns()[0].GetAsString());
+  EXPECT_EQ("https://*/*", script.url_patterns()[1].GetAsString());
+
+  // Make sure the files actually exist on disk.
+  EXPECT_TRUE(file_util::PathExists(
+      extension->path().Append(script.js_scripts()[0].relative_path())));
+  EXPECT_TRUE(file_util::PathExists(
+      extension->path().AppendASCII(Extension::kManifestFilename)));
+}
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc
index 6c6259f579bdc19b58091a5d83a5930bfcdbdd03..cb5b4e42c9a284147d8ec7e4d5247de7dc86dca2 100644
--- a/chrome/browser/extensions/crx_installer.cc
+++ b/chrome/browser/extensions/crx_installer.cc
@@ -11,6 +11,7 @@
 #include "base/task.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/extensions/convert_user_script.h"
 #include "chrome/browser/extensions/extension_file_util.h"
 #include "chrome/common/extensions/extension_error_reporter.h"
 #include "chrome/common/notification_service.h"
@@ -31,41 +32,56 @@ void CrxInstaller::Start(const FilePath& crx_path,
                          const FilePath& install_directory,
                          Extension::Location install_source,
                          const std::string& expected_id,
-                         bool delete_crx,
+                         bool delete_source,
                          bool allow_privilege_increase,
                          ExtensionsService* frontend,
                          ExtensionInstallUI* client) {
   // Note: We don't keep a reference because this object manages its own
   // lifetime.
-  new CrxInstaller(crx_path, install_directory, install_source, expected_id,
-                   delete_crx, allow_privilege_increase, frontend, client);
+  CrxInstaller* installer = new CrxInstaller(crx_path, install_directory,
+                                             delete_source, frontend,
+                                             client);
+  installer->install_source_ = install_source;
+  installer->expected_id_ = expected_id;
+  installer->allow_privilege_increase_ = allow_privilege_increase;
+
+  installer->unpacker_ = new SandboxedExtensionUnpacker(
+      installer->source_file_, g_browser_process->resource_dispatcher_host(),
+      installer);
+
+  ChromeThread::PostTask(
+      ChromeThread::FILE, FROM_HERE,
+      NewRunnableMethod(installer->unpacker_, &SandboxedExtensionUnpacker::Start));
+}
+
+void CrxInstaller::InstallUserScript(const FilePath& source_file,
+                                     const GURL& original_url,
+                                     const FilePath& install_directory,
+                                     bool delete_source,
+                                     ExtensionsService* frontend,
+                                     ExtensionInstallUI* client) {
+  CrxInstaller* installer = new CrxInstaller(source_file, install_directory,
+                                             delete_source, frontend, client);
+  installer->original_url_ = original_url;
+
+  ChromeThread::PostTask(
+      ChromeThread::FILE, FROM_HERE,
+      NewRunnableMethod(installer, &CrxInstaller::ConvertUserScriptOnFileThread));
 }
 
-CrxInstaller::CrxInstaller(const FilePath& crx_path,
+CrxInstaller::CrxInstaller(const FilePath& source_file,
                            const FilePath& install_directory,
-                           Extension::Location install_source,
-                           const std::string& expected_id,
-                           bool delete_crx,
-                           bool allow_privilege_increase,
+                           bool delete_source,
                            ExtensionsService* frontend,
                            ExtensionInstallUI* client)
-    : crx_path_(crx_path),
+    : source_file_(source_file),
       install_directory_(install_directory),
-      install_source_(install_source),
-      expected_id_(expected_id),
-      delete_crx_(delete_crx),
-      allow_privilege_increase_(allow_privilege_increase),
+      install_source_(Extension::INTERNAL),
+      delete_source_(delete_source),
+      allow_privilege_increase_(false),
       frontend_(frontend),
       client_(client) {
-
   extensions_enabled_ = frontend_->extensions_enabled();
-
-  unpacker_ = new SandboxedExtensionUnpacker(
-      crx_path, g_browser_process->resource_dispatcher_host(), this);
-
-  ChromeThread::PostTask(
-      ChromeThread::FILE, FROM_HERE,
-      NewRunnableMethod(unpacker_, &SandboxedExtensionUnpacker::Start));
 }
 
 CrxInstaller::~CrxInstaller() {
@@ -78,11 +94,23 @@ CrxInstaller::~CrxInstaller() {
         NewRunnableFunction(&DeleteFileHelper, temp_dir_, true));
   }
 
-  if (delete_crx_) {
+  if (delete_source_) {
     ChromeThread::PostTask(
         ChromeThread::FILE, FROM_HERE,
-        NewRunnableFunction(&DeleteFileHelper, crx_path_, false));
+        NewRunnableFunction(&DeleteFileHelper, source_file_, false));
+  }
+}
+
+void CrxInstaller::ConvertUserScriptOnFileThread() {
+  std::string error;
+  Extension* extension = ConvertUserScriptToExtension(source_file_,
+                                                      original_url_, &error);
+  if (!extension) {
+    ReportFailureFromFileThread(error);
+    return;
   }
+
+  OnUnpackSuccess(extension->path(), extension->path(), extension);
 }
 
 void CrxInstaller::OnUnpackFailure(const std::string& error_message) {
@@ -102,7 +130,6 @@ void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir,
   // The unpack dir we don't have to delete explicity since it is a child of
   // the temp dir.
   unpacked_extension_root_ = extension_dir;
-  DCHECK(file_util::ContainsPath(temp_dir_, unpacked_extension_root_));
 
   // Determine whether to allow installation. We always allow themes and
   // external installs.
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index a0e50efc74a32fe3fb838a425cd6808a5e9beceb..bd78cf6006c527b8f060bfa4e27c5f5ee01ef576 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -38,7 +38,7 @@ class CrxInstaller :
     public SandboxedExtensionUnpackerClient,
     public ExtensionInstallUI::Delegate {
  public:
-  // Starts the installation of the crx file in |crx_path| into
+  // Starts the installation of the crx file in |source_file| into
   // |install_directory|.
   //
   // Other params:
@@ -52,15 +52,25 @@ class CrxInstaller :
   //  client: Optional. If specified, will be used to confirm installation and
   //          also notified of success/fail. Note that we hold a reference to
   //          this, so it can outlive its creator (eg the UI).
-  static void Start(const FilePath& crx_path,
+  static void Start(const FilePath& source_file,
                     const FilePath& install_directory,
                     Extension::Location install_source,
                     const std::string& expected_id,
-                    bool delete_crx,
+                    bool delete_source,
                     bool allow_privilege_increase,
                     ExtensionsService* frontend,
                     ExtensionInstallUI* client);
 
+  // Starts the installation of the user script file in |source_file| into
+  // |install_directory|. The script will be converted to an extension.
+  // See Start() for argument descriptions.
+  static void InstallUserScript(const FilePath& source_file,
+                                const GURL& original_url,
+                                const FilePath& install_directory,
+                                bool delete_source,
+                                ExtensionsService* frontend,
+                                ExtensionInstallUI* client);
+
   // Given the path to the large icon from an extension, read it if present and
   // decode it into result.
   static void DecodeInstallIcon(const FilePath& large_icon_path,
@@ -71,16 +81,16 @@ class CrxInstaller :
   virtual void AbortInstall();
 
  private:
-  CrxInstaller(const FilePath& crx_path,
+  CrxInstaller(const FilePath& source_file,
                const FilePath& install_directory,
-               Extension::Location install_source,
-               const std::string& expected_id,
-               bool delete_crx,
-               bool allow_privilege_increase,
+               bool delete_source,
                ExtensionsService* frontend,
                ExtensionInstallUI* client);
   ~CrxInstaller();
 
+  // Converts the source user script to an extension.
+  void ConvertUserScriptOnFileThread();
+
   // SandboxedExtensionUnpackerClient
   virtual void OnUnpackFailure(const std::string& error_message);
   virtual void OnUnpackSuccess(const FilePath& temp_dir,
@@ -103,8 +113,11 @@ class CrxInstaller :
   void ReportSuccessFromFileThread();
   void ReportSuccessFromUIThread();
 
-  // The crx file we're installing.
-  FilePath crx_path_;
+  // The file we're installing.
+  FilePath source_file_;
+
+  // The URL the file was downloaded from. Only used for user scripts.
+  GURL original_url_;
 
   // The directory extensions are installed to.
   FilePath install_directory_;
@@ -123,8 +136,8 @@ class CrxInstaller :
   // allowed.
   bool extensions_enabled_;
 
-  // Whether we're supposed to delete the source crx file on destruction.
-  bool delete_crx_;
+  // Whether we're supposed to delete the source file on destruction.
+  bool delete_source_;
 
   // Whether privileges should be allowed to silently increaes from any
   // previously installed version of the extension.
diff --git a/chrome/browser/extensions/user_script_master.cc b/chrome/browser/extensions/user_script_master.cc
index 69fd746ddbd9999ba88d8a447b28f4e8f4201b7d..c9228ffc462880d9e97b953ab8eb2182f37410fa 100644
--- a/chrome/browser/extensions/user_script_master.cc
+++ b/chrome/browser/extensions/user_script_master.cc
@@ -51,7 +51,11 @@ bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
 
   static const base::StringPiece kUserScriptBegin("// ==UserScript==");
   static const base::StringPiece kUserScriptEng("// ==/UserScript==");
+  static const base::StringPiece kNamespaceDeclaration("// @namespace ");
+  static const base::StringPiece kNameDeclaration("// @name ");
+  static const base::StringPiece kDescriptionDeclaration("// @description ");
   static const base::StringPiece kIncludeDeclaration("// @include ");
+  static const base::StringPiece kExcludeDeclaration("// @exclude ");
   static const base::StringPiece kMatchDeclaration("// @match ");
   static const base::StringPiece kRunAtDeclaration("// @run-at ");
   static const base::StringPiece kRunAtDocumentStartValue("document-start");
@@ -79,6 +83,16 @@ bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
         ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
         ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
         script->add_glob(value);
+      } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
+        ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+        ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+        script->add_exclude_glob(value);
+      } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
+        script->set_name_space(value);
+      } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
+        script->set_name(value);
+      } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
+        script->set_description(value);
       } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
         URLPattern pattern;
         if (!pattern.Parse(value))
@@ -97,10 +111,6 @@ bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
     line_start = line_end + 1;
   }
 
-  // It is probably a mistake to declare both @include and @match rules.
-  if (script->globs().size() > 0 && script->url_patterns().size() > 0)
-    return false;
-
   // If no patterns were specified, default to @include *. This is what
   // Greasemonkey does.
   if (script->globs().size() == 0 && script->url_patterns().size() == 0)
diff --git a/chrome/browser/extensions/user_script_master.h b/chrome/browser/extensions/user_script_master.h
index a0624481bdd117d08463bb23fa3f3e00c3b5b3a1..270a69057128b3e770e3b9a470640086978b0e14 100644
--- a/chrome/browser/extensions/user_script_master.h
+++ b/chrome/browser/extensions/user_script_master.h
@@ -61,6 +61,7 @@ class UserScriptMaster : public base::RefCountedThreadSafe<UserScriptMaster>,
   FRIEND_TEST(UserScriptMasterTest, Parse5);
   FRIEND_TEST(UserScriptMasterTest, Parse6);
 
+ public:
   // We reload user scripts on the file thread to prevent blocking the UI.
   // ScriptReloader lives on the file thread and does the reload
   // work, and then sends a message back to its master with a new SharedMemory*.
@@ -118,6 +119,7 @@ class UserScriptMaster : public base::RefCountedThreadSafe<UserScriptMaster>,
     DISALLOW_COPY_AND_ASSIGN(ScriptReloader);
   };
 
+ private:
   // DirectoryWatcher::Delegate implementation.
   virtual void OnDirectoryChanged(const FilePath& path);
 
diff --git a/chrome/browser/extensions/user_script_master_unittest.cc b/chrome/browser/extensions/user_script_master_unittest.cc
index 840d8a3af728ffc2f1fa412cd3c4ef7a3e07f9b4..29faaea213bd41e9a7b266017a050cc79969a4a1 100644
--- a/chrome/browser/extensions/user_script_master_unittest.cc
+++ b/chrome/browser/extensions/user_script_master_unittest.cc
@@ -213,8 +213,8 @@ TEST_F(UserScriptMasterTest, Parse6) {
     "// @match  \t http://mail.yahoo.com/*\n"
     "// ==/UserScript==\n");
 
-  // Not allowed to mix @include and @value.
+  // Allowed to match @include and @match.
   UserScript script;
-  EXPECT_FALSE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
+  EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
       text, &script));
 }
diff --git a/chrome/browser/renderer_host/buffered_resource_handler.cc b/chrome/browser/renderer_host/buffered_resource_handler.cc
index 6022e1f139a276c49e7c464e22782ebab9f802a2..f3ca82f758b3b0964cf7609049760c807659e6e7 100644
--- a/chrome/browser/renderer_host/buffered_resource_handler.cc
+++ b/chrome/browser/renderer_host/buffered_resource_handler.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/renderer_host/resource_dispatcher_host.h"
 #include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h"
 #include "chrome/browser/renderer_host/x509_user_cert_resource_handler.h"
+#include "chrome/common/extensions/user_script.h"
 #include "chrome/common/url_constants.h"
 #include "net/base/io_buffer.h"
 #include "net/base/mime_sniffer.h"
@@ -441,6 +442,10 @@ bool BufferedResourceHandler::ShouldDownload(bool* need_plugin_list) {
       return true;
   }
 
+  // Special-case user scripts to get downloaded instead of viewed.
+  if (UserScript::HasUserScriptFileExtension(request_->url()))
+    return true;
+
   // MIME type checking.
   if (net::IsSupportedMimeType(type))
     return false;
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 8b4a857ffb6edbea8ae5ed28a20558aa82c91f9b..e8c92f508fc0d4117dc5dc6d99323c59c278f273 100755
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1317,6 +1317,8 @@
         'browser/download/save_types.h',
         'browser/encoding_menu_controller.cc',
         'browser/encoding_menu_controller.h',
+        'browser/extensions/convert_user_script.cc',
+        'browser/extensions/convert_user_script.h',
         'browser/extensions/crashed_extension_infobar.cc',
         'browser/extensions/crashed_extension_infobar.h',
         'browser/extensions/crx_installer.cc',
@@ -4551,6 +4553,7 @@
         'browser/download/download_request_manager_unittest.cc',
         'browser/download/save_package_unittest.cc',
         'browser/encoding_menu_controller_unittest.cc',
+        'browser/extensions/convert_user_script_unittest.cc',
         'browser/extensions/extension_file_util_unittest.cc',
         'browser/extensions/extension_messages_unittest.cc',
         'browser/extensions/extension_process_manager_unittest.cc',
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index c6172321800fedc4a807fbb9957394f9591cb0b3..0aad3de3d8bfdb451ad3fc489ea9d7d90cc56c20 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -248,6 +248,17 @@ bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
     result->add_url_pattern(pattern);
   }
 
+  // include/exclude globs (mostly for Greasemonkey compat)
+  if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
+                       error, &UserScript::add_glob, result)) {
+      return false;
+  }
+
+  if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
+                       error, &UserScript::add_exclude_glob, result)) {
+      return false;
+  }
+
   // js and css keys
   ListValue* js = NULL;
   if (content_script->HasKey(keys::kJs) &&
@@ -311,6 +322,38 @@ bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
   return true;
 }
 
+bool Extension::LoadGlobsHelper(
+    const DictionaryValue* content_script,
+    int content_script_index,
+    const wchar_t* globs_property_name,
+    std::string* error,
+    void (UserScript::*add_method) (const std::string& glob),
+    UserScript *instance) {
+  if (!content_script->HasKey(globs_property_name))
+    return true;  // they are optional
+
+  ListValue* list = NULL;
+  if (!content_script->GetList(globs_property_name, &list)) {
+    *error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidGlobList,
+        IntToString(content_script_index), WideToASCII(globs_property_name));
+    return false;
+  }
+
+  for (size_t i = 0; i < list->GetSize(); ++i) {
+    std::string glob;
+    if (!list->GetString(i, &glob)) {
+      *error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidGlob,
+          IntToString(content_script_index), WideToASCII(globs_property_name),
+          IntToString(i));
+      return false;
+    }
+
+    (instance->*add_method)(glob);
+  }
+
+  return true;
+}
+
 ExtensionAction* Extension::LoadExtensionActionHelper(
     const DictionaryValue* extension_action, std::string* error) {
   scoped_ptr<ExtensionAction> result(new ExtensionAction());
@@ -450,7 +493,8 @@ ExtensionResource Extension::GetResource(const FilePath& extension_path,
 }
 
 Extension::Extension(const FilePath& path)
-    : is_theme_(false), background_page_ready_(false) {
+    : converted_from_user_script_(false), is_theme_(false),
+      background_page_ready_(false) {
   DCHECK(path.IsAbsolute());
   location_ = INVALID;
 
@@ -668,6 +712,10 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
     }
   }
 
+  // Initialize converted_from_user_script (if present)
+  source.GetBoolean(keys::kConvertedFromUserScript,
+                    &converted_from_user_script_);
+
   // Initialize icons (if present).
   if (source.HasKey(keys::kIcons)) {
     DictionaryValue* icons_value = NULL;
@@ -943,6 +991,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
       if (!LoadUserScriptHelper(content_script, i, error, &script))
         return false;  // Failed to parse script context definition
       script.set_extension_id(id());
+      if (converted_from_user_script_)
+        script.set_emulate_greasemonkey(true);
       content_scripts_.push_back(script);
     }
   }
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 050515b9290821f20c670ec14c47c92ec939acd1..d240388cbb23506ae20a234975bd7f94ecabd86b 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -195,6 +195,9 @@ class Extension {
   const std::string& name() const { return name_; }
   const std::string& public_key() const { return public_key_; }
   const std::string& description() const { return description_; }
+  bool converted_from_user_script() const {
+    return converted_from_user_script_;
+  }
   const UserScriptList& content_scripts() const { return content_scripts_; }
   ExtensionAction* page_action() const { return page_action_.get(); }
   ExtensionAction* browser_action() const { return browser_action_.get(); }
@@ -290,6 +293,15 @@ class Extension {
                             std::string* error,
                             UserScript* result);
 
+  // Helper method that loads either the include_globs or exclude_globs list
+  // from an entry in the content_script lists of the manifest.
+  bool LoadGlobsHelper(const DictionaryValue* content_script,
+                       int content_script_index,
+                       const wchar_t* globs_property_name,
+                       std::string* error,
+                       void (UserScript::*add_method) (const std::string& glob),
+                       UserScript *instance);
+
   // Helper method to load an ExtensionAction from the page_action or
   // browser_action entries in the manifest.
   ExtensionAction* LoadExtensionActionHelper(
@@ -325,6 +337,10 @@ class Extension {
   // An optional longer description of the extension.
   std::string description_;
 
+  // True if the extension was generated from a user script. (We show slightly
+  // different UI if so).
+  bool converted_from_user_script_;
+
   // Paths to the content scripts the extension contains.
   UserScriptList content_scripts_;
 
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 59e7c1492ebaf5a58aae6bec31ce0063075cccb4..0dbab1f6bc0b531ca8522626f3677f703af6f4ac 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -10,12 +10,15 @@ const wchar_t* kBackground = L"background_page";
 const wchar_t* kBrowserAction = L"browser_action";
 const wchar_t* kChromeURLOverrides = L"chrome_url_overrides";
 const wchar_t* kContentScripts = L"content_scripts";
+const wchar_t* kConvertedFromUserScript = L"converted_from_user_script";
 const wchar_t* kCss = L"css";
 const wchar_t* kDefaultLocale = L"default_locale";
 const wchar_t* kDescription = L"description";
 const wchar_t* kIcons = L"icons";
 const wchar_t* kJs = L"js";
 const wchar_t* kMatches = L"matches";
+const wchar_t* kIncludeGlobs = L"include_globs";
+const wchar_t* kExcludeGlobs = L"exclude_globs";
 const wchar_t* kName = L"name";
 const wchar_t* kPageActionId = L"id";
 const wchar_t* kPageAction = L"page_action";
@@ -76,6 +79,10 @@ const char* kInvalidCssList =
     "Required value 'content_scripts[*].css is invalid.";
 const char* kInvalidDescription =
     "Invalid value for 'description'.";
+const char* kInvalidGlobList =
+    "Invalid value for 'content_scripts[*].*'.";
+const char* kInvalidGlob =
+    "Invalid value for 'content_scripts[*].*[*]'.";
 const char* kInvalidIcons =
     "Invalid value for 'icons'.";
 const char* kInvalidIconPath =
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index b7826a79cbf662e3934480f47bb51ff4321fc1be..d44f7b9668cd010e4b3b8ee73fc05c4b548238d2 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -11,10 +11,13 @@ namespace extension_manifest_keys {
   extern const wchar_t* kBrowserAction;
   extern const wchar_t* kChromeURLOverrides;
   extern const wchar_t* kContentScripts;
+  extern const wchar_t* kConvertedFromUserScript;
   extern const wchar_t* kCss;
   extern const wchar_t* kDefaultLocale;
   extern const wchar_t* kDescription;
+  extern const wchar_t* kExcludeGlobs;
   extern const wchar_t* kIcons;
+  extern const wchar_t* kIncludeGlobs;
   extern const wchar_t* kJs;
   extern const wchar_t* kMatches;
   extern const wchar_t* kName;
@@ -70,6 +73,8 @@ namespace extension_manifest_errors {
   extern const char* kInvalidDescription;
   extern const char* kInvalidIcons;
   extern const char* kInvalidIconPath;
+  extern const char* kInvalidGlobList;
+  extern const char* kInvalidGlob;
   extern const char* kInvalidJs;
   extern const char* kInvalidJsList;
   extern const char* kInvalidKey;
diff --git a/chrome/common/extensions/extension_error_utils.cc b/chrome/common/extensions/extension_error_utils.cc
index 724cdcd78e5e8d24a910e31156899404b21dd697..0256c8d8e634a8ab80c5faac481e016de4485866 100644
--- a/chrome/common/extensions/extension_error_utils.cc
+++ b/chrome/common/extensions/extension_error_utils.cc
@@ -8,7 +8,7 @@
 
 std::string ExtensionErrorUtils::FormatErrorMessage(
     const std::string& format,
-    const std::string s1) {
+    const std::string& s1) {
   std::string ret_val = format;
   ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
   return ret_val;
@@ -16,10 +16,22 @@ std::string ExtensionErrorUtils::FormatErrorMessage(
 
 std::string ExtensionErrorUtils::FormatErrorMessage(
     const std::string& format,
-    const std::string s1,
-    const std::string s2) {
+    const std::string& s1,
+    const std::string& s2) {
   std::string ret_val = format;
   ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
   ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
   return ret_val;
 }
+
+std::string ExtensionErrorUtils::FormatErrorMessage(
+    const std::string& format,
+    const std::string& s1,
+    const std::string& s2,
+    const std::string& s3) {
+  std::string ret_val = format;
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
+  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s3);
+  return ret_val;
+}
diff --git a/chrome/common/extensions/extension_error_utils.h b/chrome/common/extensions/extension_error_utils.h
index f9f1e7c20185f3c2f2c89c327f01a449787204a2..fdd9b03b57085adf6439eda0c9d16c826163886a 100644
--- a/chrome/common/extensions/extension_error_utils.h
+++ b/chrome/common/extensions/extension_error_utils.h
@@ -9,16 +9,18 @@
 
 class ExtensionErrorUtils {
 public:
-  // Creates an error messages from a pattern. Places first instance if "*"
-  // with |s1|.
+  // Creates an error messages from a pattern.
   static std::string FormatErrorMessage(const std::string& format,
-    const std::string s1);
+    const std::string& s1);
 
-  // Creates an error messages from a pattern. Places first instance if "*"
-  // with |s1| and second instance of "*" with |s2|.
   static std::string FormatErrorMessage(const std::string& format,
-    const std::string s1,
-    const std::string s2);
+    const std::string& s1,
+    const std::string& s2);
+
+  static std::string FormatErrorMessage(const std::string& format,
+    const std::string& s1,
+    const std::string& s2,
+    const std::string& s3);
 };
 
 #endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_ERROR_UTILS_H_
diff --git a/chrome/common/extensions/user_script.cc b/chrome/common/extensions/user_script.cc
index df34e529114551555287fd7cffd219ee65df13fb..10d0b60eef0d06167703de1bec38604108cc5b4b 100644
--- a/chrome/common/extensions/user_script.cc
+++ b/chrome/common/extensions/user_script.cc
@@ -7,21 +7,59 @@
 #include "base/pickle.h"
 #include "base/string_util.h"
 
-bool UserScript::MatchesUrl(const GURL& url) const {
-  for (std::vector<std::string>::const_iterator glob = globs_.begin();
-       glob != globs_.end(); ++glob) {
-    if (MatchPattern(url.spec(), *glob))
+namespace {
+static bool UrlMatchesPatterns(const UserScript::PatternList* patterns,
+                               const GURL& url) {
+  for (UserScript::PatternList::const_iterator pattern = patterns->begin();
+       pattern != patterns->end(); ++pattern) {
+    if (pattern->MatchesUrl(url))
       return true;
   }
 
-  for (PatternList::const_iterator pattern = url_patterns_.begin();
-       pattern != url_patterns_.end(); ++pattern) {
-    if (pattern->MatchesUrl(url))
+  return false;
+}
+
+static bool UrlMatchesGlobs(const std::vector<std::string>* globs,
+                            const GURL& url) {
+  for (std::vector<std::string>::const_iterator glob = globs->begin();
+       glob != globs->end(); ++glob) {
+    if (MatchPattern(url.spec(), *glob))
       return true;
   }
 
   return false;
 }
+}
+
+const char UserScript::kFileExtension[] = ".user.js";
+
+bool UserScript::HasUserScriptFileExtension(const GURL& url) {
+  return EndsWith(url.ExtractFileName(), kFileExtension, false);
+}
+
+bool UserScript::HasUserScriptFileExtension(const FilePath& path) {
+  static FilePath extension(FilePath().AppendASCII(kFileExtension));
+  return EndsWith(path.BaseName().value(), extension.value(), false);
+}
+
+bool UserScript::MatchesUrl(const GURL& url) const {
+  if (url_patterns_.size() > 0) {
+    if (!UrlMatchesPatterns(&url_patterns_, url))
+      return false;
+  }
+
+  if (globs_.size() > 0) {
+    if (!UrlMatchesGlobs(&globs_, url))
+      return false;
+  }
+
+  if (exclude_globs_.size() > 0) {
+    if (UrlMatchesGlobs(&exclude_globs_, url))
+      return false;
+  }
+
+  return true;
+}
 
 void UserScript::File::Pickle(::Pickle* pickle) const {
   pickle->WriteString(url_.spec());
@@ -43,10 +81,17 @@ void UserScript::Pickle(::Pickle* pickle) const {
   // Write the extension id.
   pickle->WriteString(extension_id());
 
+  // Write Greasemonkey emulation.
+  pickle->WriteBool(emulate_greasemonkey());
+
   // Write globs.
+  std::vector<std::string>::const_iterator glob;
   pickle->WriteSize(globs_.size());
-  for (std::vector<std::string>::const_iterator glob = globs_.begin();
-       glob != globs_.end(); ++glob) {
+  for (glob = globs_.begin(); glob != globs_.end(); ++glob) {
+    pickle->WriteString(*glob);
+  }
+  pickle->WriteSize(exclude_globs_.size());
+  for (glob = exclude_globs_.begin(); glob != exclude_globs_.end(); ++glob) {
     pickle->WriteString(*glob);
   }
 
@@ -82,10 +127,12 @@ void UserScript::Unpickle(const ::Pickle& pickle, void** iter) {
   // Read the extension ID.
   CHECK(pickle.ReadString(iter, &extension_id_));
 
+  // Read Greasemonkey emulation.
+  CHECK(pickle.ReadBool(iter, &emulate_greasemonkey_));
+
   // Read globs.
   size_t num_globs = 0;
   CHECK(pickle.ReadSize(iter, &num_globs));
-
   globs_.clear();
   for (size_t i = 0; i < num_globs; ++i) {
     std::string glob;
@@ -93,6 +140,14 @@ void UserScript::Unpickle(const ::Pickle& pickle, void** iter) {
     globs_.push_back(glob);
   }
 
+  CHECK(pickle.ReadSize(iter, &num_globs));
+  exclude_globs_.clear();
+  for (size_t i = 0; i < num_globs; ++i) {
+    std::string glob;
+    CHECK(pickle.ReadString(iter, &glob));
+    exclude_globs_.push_back(glob);
+  }
+
   // Read url patterns.
   size_t num_patterns = 0;
   CHECK(pickle.ReadSize(iter, &num_patterns));
diff --git a/chrome/common/extensions/user_script.h b/chrome/common/extensions/user_script.h
index bdd1e5435fdd7f201eee32617192cf5195af2c22..adcfa3fcf27a507a1ab8c2c454a4036c5371b5a6 100644
--- a/chrome/common/extensions/user_script.h
+++ b/chrome/common/extensions/user_script.h
@@ -22,6 +22,13 @@ class UserScript {
  public:
   typedef std::vector<URLPattern> PatternList;
 
+  // The file extension for standalone user scripts.
+  static const char kFileExtension[];
+
+  // Check if a file or URL has the user script file extension.
+  static bool HasUserScriptFileExtension(const GURL& url);
+  static bool HasUserScriptFileExtension(const FilePath& path);
+
   // Locations that user scripts can be run inside the document.
   enum RunLocation {
     DOCUMENT_START,  // After the documentElemnet is created, but before
@@ -92,20 +99,45 @@ class UserScript {
 
   typedef std::vector<File> FileList;
 
-  // Constructor. Default the run location to document idle, which is similar
-  // to Greasemonkey but should result in better page load times for fast-
-  // loading pages.
-  UserScript() : run_location_(DOCUMENT_IDLE) {}
+  // Constructor. Default the run location to document end, which is like
+  // Greasemonkey and probably more useful for typical scripts.
+  UserScript()
+    : run_location_(DOCUMENT_IDLE), emulate_greasemonkey_(false) {
+  }
+
+  const std::string& name_space() const { return name_space_; }
+  void set_name_space(const std::string& name_space) {
+    name_space_ = name_space;
+  }
+
+  const std::string& name() const { return name_; }
+  void set_name(const std::string& name) { name_ = name; }
+
+  const std::string& description() const { return description_; }
+  void set_description(const std::string& description) {
+    description_ = description;
+  }
 
   // The place in the document to run the script.
   RunLocation run_location() const { return run_location_; }
   void set_run_location(RunLocation location) { run_location_ = location; }
 
+  // Whether to emulate greasemonkey when running this script.
+  bool emulate_greasemonkey() const { return emulate_greasemonkey_; }
+  void set_emulate_greasemonkey(bool val) { emulate_greasemonkey_ = val; }
+
   // The globs, if any, that determine which pages this script runs against.
   // These are only used with "standalone" Greasemonkey-like user scripts.
   const std::vector<std::string>& globs() const { return globs_; }
   void add_glob(const std::string& glob) { globs_.push_back(glob); }
   void clear_globs() { globs_.clear(); }
+  const std::vector<std::string>& exclude_globs() const {
+    return exclude_globs_;
+  }
+  void add_exclude_glob(const std::string& glob) {
+    exclude_globs_.push_back(glob);
+  }
+  void clear_exclude_globs() { exclude_globs_.clear(); }
 
   // The URLPatterns, if any, that determine which pages this script runs
   // against.
@@ -145,9 +177,20 @@ class UserScript {
   // The location to run the script inside the document.
   RunLocation run_location_;
 
+  // The namespace of the script. This is used by Greasemonkey in the same way
+  // as XML namespaces. Only used when parsing Greasemonkey-style scripts.
+  std::string name_space_;
+
+  // The script's name. Only used when parsing Greasemonkey-style scripts.
+  std::string name_;
+
+  // A longer description. Only used when parsing Greasemonkey-style scripts.
+  std::string description_;
+
   // Greasemonkey-style globs that determine pages to inject the script into.
   // These are only used with standalone scripts.
   std::vector<std::string> globs_;
+  std::vector<std::string> exclude_globs_;
 
   // URLPatterns that determine pages to inject the script into. These are
   // only used with scripts that are part of extensions.
@@ -162,6 +205,10 @@ class UserScript {
   // The ID of the extension this script is a part of, if any. Can be empty if
   // the script is a "standlone" user script.
   std::string extension_id_;
+
+  // Whether we should try to emulate Greasemonkey's APIs when running this
+  // script.
+  bool emulate_greasemonkey_;
 };
 
 typedef std::vector<UserScript> UserScriptList;
diff --git a/chrome/common/extensions/user_script_unittest.cc b/chrome/common/extensions/user_script_unittest.cc
index 58ef77e4c490737dda62527e3c92a127caf3a3a7..7b5cc6d39e713c48de18a24d9a88036557276b10 100644
--- a/chrome/common/extensions/user_script_unittest.cc
+++ b/chrome/common/extensions/user_script_unittest.cc
@@ -21,6 +21,10 @@ TEST(UserScriptTest, Match1) {
   EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.yahoo.com/bar")));
   EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.msn.com/baz")));
   EXPECT_FALSE(script.MatchesUrl(GURL("http://www.hotmail.com")));
+
+  script.add_exclude_glob("*foo*");
+  EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.google.com")));
+  EXPECT_FALSE(script.MatchesUrl(GURL("http://mail.google.com/foo")));
 }
 
 TEST(UserScriptTest, Match2) {
@@ -70,6 +74,36 @@ TEST(UserScriptTest, Match6) {
   // NOTE: URLPattern is tested more extensively in url_pattern_unittest.cc.
 }
 
+TEST(UserScriptTest, UrlPatternGlobInteraction) {
+  // If there are both, match intersection(union(globs), union(urlpatterns)).
+  UserScript script;
+  
+  URLPattern pattern;
+  ASSERT_TRUE(pattern.Parse("http://www.google.com/*"));
+  script.add_url_pattern(pattern);
+
+  script.add_glob("*bar*");
+
+  // No match, because it doesn't match the glob.
+  EXPECT_FALSE(script.MatchesUrl(GURL("http://www.google.com/foo")));
+
+  script.add_exclude_glob("*baz*");
+
+  // No match, because it matches the exclude glob.
+  EXPECT_FALSE(script.MatchesUrl(GURL("http://www.google.com/baz")));
+
+  // Match, because it matches the glob, doesn't match the exclude glob.
+  EXPECT_TRUE(script.MatchesUrl(GURL("http://www.google.com/bar")));
+
+  // Try with just a single exclude glob.
+  script.clear_globs();
+  EXPECT_TRUE(script.MatchesUrl(GURL("http://www.google.com/foo")));
+
+  // Try with no globs or exclude globs.
+  script.clear_exclude_globs();
+  EXPECT_TRUE(script.MatchesUrl(GURL("http://www.google.com/foo")));
+}
+
 TEST(UserScriptTest, Pickle) {
   URLPattern pattern1;
   URLPattern pattern2;
diff --git a/chrome/renderer/resources/greasemonkey_api.js b/chrome/renderer/resources/greasemonkey_api.js
index 7b829ad35f733aaef222d07db5b3c8be68bb41e8..52f7560bf9f6812781f6102c9c9f0d6564353e90 100644
--- a/chrome/renderer/resources/greasemonkey_api.js
+++ b/chrome/renderer/resources/greasemonkey_api.js
@@ -67,3 +67,12 @@ function GM_openInTab(url) {
 function GM_log(message) {
   window.console.log(message);
 }
+
+(function() {
+  var apis = ["GM_getValue", "GM_setValue", "GM_registerMenuCommand"];
+  for (var i = 0, api; api = apis[i]; i++) {
+    window[api] = function() {
+      console.log("%s is not supported.", api);
+    }
+  }
+})();
diff --git a/chrome/renderer/user_script_slave.cc b/chrome/renderer/user_script_slave.cc
index b71c7a8e44230b299f61e883ad903d97adae8996..473cda85626cba324f092e4ff98acfb11513a03c 100644
--- a/chrome/renderer/user_script_slave.cc
+++ b/chrome/renderer/user_script_slave.cc
@@ -51,22 +51,9 @@ int UserScriptSlave::GetIsolatedWorldId(const std::string& extension_id) {
 
 UserScriptSlave::UserScriptSlave()
     : shared_memory_(NULL),
-      script_deleter_(&scripts_),
-      user_script_start_line_(0) {
+      script_deleter_(&scripts_) {
   api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
                 IDR_GREASEMONKEY_API_JS);
-
-  // Count the number of lines that will be injected before the user script.
-  base::StringPiece::size_type pos = 0;
-  while ((pos = api_js_.find('\n', pos)) != base::StringPiece::npos) {
-    user_script_start_line_++;
-    pos++;
-  }
-
-  // NOTE: There is actually one extra line in the injected script because the
-  // function header includes a newline as well. But WebKit expects the
-  // numbering to be one-based, not zero-based, so actually *not* accounting for
-  // this extra line ends us up with the right offset.
 }
 
 bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) {
@@ -178,13 +165,16 @@ bool UserScriptSlave::InjectScripts(WebFrame* frame,
     if (!sources.empty()) {
       int isolated_world_id = 0;
 
-      if (script->is_standalone()) {
-        // For standalone scripts, we try to emulate the Greasemonkey API.
+      // Emulate Greasemonkey API for scripts that were converted to extensions
+      // and "standalone" user scripts.
+      if (script->is_standalone() || script->emulate_greasemonkey()) {
         sources.insert(sources.begin(),
             WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
-      } else {
-        // Setup chrome.self to contain an Extension object with the correct
-        // ID.
+      }
+
+      // Setup chrome.self to contain an Extension object with the correct
+      // ID.
+      if (!script->extension_id().empty()) {
         InsertInitExtensionCode(&sources, script->extension_id());
         isolated_world_id = GetIsolatedWorldId(script->extension_id());
       }
diff --git a/chrome/renderer/user_script_slave.h b/chrome/renderer/user_script_slave.h
index 8232ec4ea771d0c5884f90d7a621515ac6fd1f02..3d7c49d75fb613287276b9a5c11f88b41cf71379 100644
--- a/chrome/renderer/user_script_slave.h
+++ b/chrome/renderer/user_script_slave.h
@@ -50,11 +50,6 @@ class UserScriptSlave {
   // Greasemonkey API source that is injected with the scripts.
   base::StringPiece api_js_;
 
-  // The line number of the first line of the user script among all of the
-  // injected javascript.  This is used to make reported errors correspond with
-  // the proper line in the user script.
-  int user_script_start_line_;
-
   DISALLOW_COPY_AND_ASSIGN(UserScriptSlave);
 };
 
diff --git a/chrome/test/data/extensions/user_script_basic.user.js b/chrome/test/data/extensions/user_script_basic.user.js
new file mode 100755
index 0000000000000000000000000000000000000000..ea4f8a09a7a6edc7d66f42575d83631d0e2e2929
--- /dev/null
+++ b/chrome/test/data/extensions/user_script_basic.user.js
@@ -0,0 +1,11 @@
+// ==UserScript==
+// @name My user script
+// @namespace http://www.google.com
+// @description Does totally awesome stuff.
+// @include http://www.google.com/*
+// @include http://www.yahoo.com/*
+// @exclude *foo*
+// @match http://www.google.com/*
+// ==/UserScript==
+
+alert("Hello! This is my script.");
diff --git a/chrome/test/data/extensions/user_script_no_metadata.user.js b/chrome/test/data/extensions/user_script_no_metadata.user.js
new file mode 100755
index 0000000000000000000000000000000000000000..f8fe23e639b3509847563564c5774225cf5ac486
--- /dev/null
+++ b/chrome/test/data/extensions/user_script_no_metadata.user.js
@@ -0,0 +1 @@
+alert("This user script has no metadata, but it is also valid!");