diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-04 02:15:20 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-04 02:15:20 +0000 |
commit | 6657afa65426f10f5dc51c0ac12d573c4826cb54 (patch) | |
tree | 3b24f83dd2f834bde93b7b7e27327701814379ac | |
parent | 6c856ae733bf8d1f187ec5b93e07216ace0c4693 (diff) | |
download | chromium_src-6657afa65426f10f5dc51c0ac12d573c4826cb54.zip chromium_src-6657afa65426f10f5dc51c0ac12d573c4826cb54.tar.gz chromium_src-6657afa65426f10f5dc51c0ac12d573c4826cb54.tar.bz2 |
Add first class support for user scripts.
Original review: http://codereview.chromium.org/340057
TBR=mpcomplete@chromium.org
BUG=22103
TEST=Install a user script (such as from userscripts.org). You should get the extension install UI and the
script should show up in the extension management UI. It should also work, though some scripts use
Firefox-specific APIs and those won't work in Chromium.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30925 0039d316-1c4b-4281-b951-d872f2087c98
27 files changed, 676 insertions, 101 deletions
diff --git a/base/string_util.cc b/base/string_util.cc index a62b905..11ac623 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 1c58e91..89caf4f 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 eef8b04..bb0a280 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 0000000..70de6d9 --- /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 0000000..ebc9ee2 --- /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 0000000..b7aea54 --- /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 6c6259f..cb5b4e4 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 a0e50ef..bd78cf6 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 69fd746..c9228ff 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 a062448..270a690 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 840d8a3..29faaea 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 6022e1f..f3ca82f 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 8b4a857..e8c92f5 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 c617232..0aad3de 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 050515b..d240388 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 59e7c14..0dbab1f 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 b7826a7..d44f7b9 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 724cdcd..0256c8d8 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 f9f1e7c..fdd9b03 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 df34e52..10d0b60 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 bdd1e54..adcfa3f 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 58ef77e..7b5cc6d 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 7b829ad..52f7560 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 b71c7a8..473cda8 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 8232ec4..3d7c49d 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 0000000..ea4f8a0 --- /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 0000000..f8fe23e --- /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!");
|