summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-04 02:15:20 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-04 02:15:20 +0000
commit6657afa65426f10f5dc51c0ac12d573c4826cb54 (patch)
tree3b24f83dd2f834bde93b7b7e27327701814379ac /chrome/common/extensions
parent6c856ae733bf8d1f187ec5b93e07216ace0c4693 (diff)
downloadchromium_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
Diffstat (limited to 'chrome/common/extensions')
-rw-r--r--chrome/common/extensions/extension.cc52
-rw-r--r--chrome/common/extensions/extension.h16
-rw-r--r--chrome/common/extensions/extension_constants.cc7
-rw-r--r--chrome/common/extensions/extension_constants.h5
-rw-r--r--chrome/common/extensions/extension_error_utils.cc18
-rw-r--r--chrome/common/extensions/extension_error_utils.h16
-rw-r--r--chrome/common/extensions/user_script.cc75
-rw-r--r--chrome/common/extensions/user_script.h55
-rw-r--r--chrome/common/extensions/user_script_unittest.cc34
9 files changed, 253 insertions, 25 deletions
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;