diff options
author | georged@chromium.org <georged@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-18 21:26:24 +0000 |
---|---|---|
committer | georged@chromium.org <georged@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-18 21:26:24 +0000 |
commit | 3cfbd0eb2afd2cf0b8b248d6bb97ec39b064cd23 (patch) | |
tree | eaa3eddbc15cf6bac88d04c4766778fd545f95c0 | |
parent | 836827406c7135b0225474d24b9d08dd8edff27a (diff) | |
download | chromium_src-3cfbd0eb2afd2cf0b8b248d6bb97ec39b064cd23.zip chromium_src-3cfbd0eb2afd2cf0b8b248d6bb97ec39b064cd23.tar.gz chromium_src-3cfbd0eb2afd2cf0b8b248d6bb97ec39b064cd23.tar.bz2 |
Review URL: http://codereview.chromium.org/42288
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12016 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 548 insertions, 214 deletions
diff --git a/chrome/browser/extensions/extension.cc b/chrome/browser/extensions/extension.cc index 75d8930..924c4ed 100644 --- a/chrome/browser/extensions/extension.cc +++ b/chrome/browser/extensions/extension.cc @@ -19,6 +19,7 @@ const wchar_t* Extension::kDescriptionKey = L"description"; const wchar_t* Extension::kFormatVersionKey = L"format_version"; const wchar_t* Extension::kIdKey = L"id"; const wchar_t* Extension::kJsKey = L"js"; +const wchar_t* Extension::kCssKey = L"css"; const wchar_t* Extension::kMatchesKey = L"matches"; const wchar_t* Extension::kNameKey = L"name"; const wchar_t* Extension::kRunAtKey = L"run_at"; @@ -39,6 +40,10 @@ const char* Extension::kInvalidContentScriptError = "Invalid value for 'content_scripts[*]'."; const char* Extension::kInvalidContentScriptsListError = "Invalid value for 'content_scripts'."; +const char* Extension::kInvalidCssError = + "Invalid value for 'content_scripts[*].css[*]'."; +const char* Extension::kInvalidCssListError = + "Required value 'content_scripts[*].css is invalid."; const char* Extension::kInvalidDescriptionError = "Invalid value for 'description'."; const char* Extension::kInvalidFormatVersionError = @@ -51,7 +56,7 @@ const char* Extension::kInvalidJsCountError = const char* Extension::kInvalidJsError = "Invalid value for 'content_scripts[*].js[*]'."; const char* Extension::kInvalidJsListError = - "Required value 'content_scripts[*].js is missing or invalid."; + "Required value 'content_scripts[*].js is invalid."; const char* Extension::kInvalidManifestError = "Manifest is missing or invalid."; const char* Extension::kInvalidMatchCountError = @@ -63,16 +68,18 @@ const char* Extension::kInvalidMatchesError = "Required value 'content_scripts[*].matches' is missing or invalid."; const char* Extension::kInvalidNameError = "Required value 'name' is missing or invalid."; +const char* Extension::kInvalidPluginsDirError = + "Invalid value for 'plugins_dir'."; const char* Extension::kInvalidRunAtError = "Invalid value for 'content_scripts[*].run_at'."; +const char* Extension::kInvalidToolstripError = + "Invalid value for 'toolstrip'."; const char* Extension::kInvalidVersionError = "Required value 'version' is missing or invalid."; const char* Extension::kInvalidZipHashError = "Required key 'zip_hash' is missing or invalid."; -const char* Extension::kInvalidPluginsDirError = - "Invalid value for 'plugins_dir'."; -const char* Extension::kInvalidToolstripError = - "Invalid value for 'toolstrip'."; +const char* Extension::kMissingFileError = + "At least one js or css file is required for 'content_scripts[*]'."; const size_t Extension::kIdSize = 20; // SHA1 (160 bits) == 20 bytes @@ -185,6 +192,135 @@ Extension::Extension(const FilePath& path) { #endif } +// Helper method that loads a UserScript object from a dictionary in the +// content_script list of the manifest. +bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script, + int definition_index, std::string* error, + UserScript* result) { + // run_at + if (content_script->HasKey(kRunAtKey)) { + std::string run_location; + if (!content_script->GetString(kRunAtKey, &run_location)) { + *error = FormatErrorMessage(kInvalidRunAtError, + IntToString(definition_index)); + return false; + } + + if (run_location == kRunAtDocumentStartValue) { + result->set_run_location(UserScript::DOCUMENT_START); + } else if (run_location == kRunAtDocumentEndValue) { + result->set_run_location(UserScript::DOCUMENT_END); + } else { + *error = FormatErrorMessage(kInvalidRunAtError, + IntToString(definition_index)); + return false; + } + } + + // matches + ListValue* matches = NULL; + if (!content_script->GetList(kMatchesKey, &matches)) { + *error = FormatErrorMessage(kInvalidMatchesError, + IntToString(definition_index)); + return false; + } + + if (matches->GetSize() == 0) { + *error = FormatErrorMessage(kInvalidMatchCountError, + IntToString(definition_index)); + return false; + } + for (size_t j = 0; j < matches->GetSize(); ++j) { + std::string match_str; + if (!matches->GetString(j, &match_str)) { + *error = FormatErrorMessage(kInvalidMatchError, + IntToString(definition_index), + IntToString(j)); + return false; + } + + URLPattern pattern; + if (!pattern.Parse(match_str)) { + *error = FormatErrorMessage(kInvalidMatchError, + IntToString(definition_index), + IntToString(j)); + return false; + } + + result->add_url_pattern(pattern); + } + + // js and css keys + ListValue* js = NULL; + if (content_script->HasKey(kJsKey) && + !content_script->GetList(kJsKey, &js)) { + *error = FormatErrorMessage(kInvalidJsListError, + IntToString(definition_index)); + return false; + } + + ListValue* css = NULL; + if (content_script->HasKey(kCssKey) && + !content_script->GetList(kCssKey, &css)) { + *error = FormatErrorMessage(kInvalidCssListError, + IntToString(definition_index)); + return false; + } + + // The manifest needs to have at least one js or css user script definition. + if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) { + *error = FormatErrorMessage(kMissingFileError, + IntToString(definition_index)); + return false; + } + + // NOTE: Only one js file is supported for now. + // TODO(aa): Add support for multiple js files. + if (js && js->GetSize() != 1) { + *error = FormatErrorMessage(kInvalidJsCountError, + IntToString(definition_index)); + return false; + } + + if (js) { + for (size_t script_index = 0; script_index < js->GetSize(); + ++script_index) { + Value* value; + std::wstring relative; + if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) { + *error = FormatErrorMessage(kInvalidJsError, + IntToString(definition_index), + IntToString(script_index)); + return false; + } + // TODO(georged): Make GetResourceURL accept wstring too + GURL url = GetResourceURL(WideToUTF8(relative)); + FilePath path = GetResourcePath(WideToUTF8(relative)); + result->js_scripts().push_back(UserScript::File(path, url)); + } + } + + if (css) { + for (size_t script_index = 0; script_index < css->GetSize(); + ++script_index) { + Value* value; + std::wstring relative; + if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) { + *error = FormatErrorMessage(kInvalidCssError, + IntToString(definition_index), + IntToString(script_index)); + return false; + } + // TODO(georged): Make GetResourceURL accept wstring too + GURL url = GetResourceURL(WideToUTF8(relative)); + FilePath path = GetResourcePath(WideToUTF8(relative)); + result->css_scripts().push_back(UserScript::File(path, url)); + } + } + + return true; +} + bool Extension::InitFromValue(const DictionaryValue& source, std::string* error) { // Check format version. @@ -300,80 +436,14 @@ bool Extension::InitFromValue(const DictionaryValue& source, for (size_t i = 0; i < list_value->GetSize(); ++i) { DictionaryValue* content_script; if (!list_value->GetDictionary(i, &content_script)) { - *error = FormatErrorMessage(kInvalidContentScriptError, IntToString(i)); - return false; - } - - ListValue* matches; - ListValue* js; - - if (!content_script->GetList(kMatchesKey, &matches)) { - *error = FormatErrorMessage(kInvalidMatchesError, IntToString(i)); - return false; - } - - if (!content_script->GetList(kJsKey, &js)) { - *error = FormatErrorMessage(kInvalidJsListError, IntToString(i)); - return false; - } - - if (matches->GetSize() == 0) { - *error = FormatErrorMessage(kInvalidMatchCountError, IntToString(i)); - return false; - } - - // NOTE: Only one file is supported right now. - if (js->GetSize() != 1) { - *error = FormatErrorMessage(kInvalidJsCountError, IntToString(i)); + *error = FormatErrorMessage(kInvalidContentScriptError, + IntToString(i)); return false; } UserScript script; - if (content_script->HasKey(kRunAtKey)) { - std::string run_location; - if (!content_script->GetString(kRunAtKey, &run_location)) { - *error = FormatErrorMessage(kInvalidRunAtError, IntToString(i)); - return false; - } - - if (run_location == kRunAtDocumentStartValue) { - script.set_run_location(UserScript::DOCUMENT_START); - } else if (run_location == kRunAtDocumentEndValue) { - script.set_run_location(UserScript::DOCUMENT_END); - } else { - *error = FormatErrorMessage(kInvalidRunAtError, IntToString(i)); - return false; - } - } - - for (size_t j = 0; j < matches->GetSize(); ++j) { - std::string match_str; - if (!matches->GetString(j, &match_str)) { - *error = FormatErrorMessage(kInvalidMatchError, IntToString(i), - IntToString(j)); - return false; - } - - URLPattern pattern; - if (!pattern.Parse(match_str)) { - *error = FormatErrorMessage(kInvalidMatchError, IntToString(i), - IntToString(j)); - return false; - } - - script.add_url_pattern(pattern); - } - - // TODO(aa): Support multiple files. - std::string file; - if (!js->GetString(0, &file)) { - *error = FormatErrorMessage(kInvalidJsError, IntToString(i), - IntToString(0)); - return false; - } - script.set_path(Extension::GetResourcePath(path(), file)); - script.set_url(Extension::GetResourceURL(url(), file)); - + if (!LoadUserScriptHelper(content_script, i, error, &script)) + return false; // Failed to parse script context definition content_scripts_.push_back(script); } } diff --git a/chrome/browser/extensions/extension.h b/chrome/browser/extensions/extension.h index 87290ca..fafe33a 100644 --- a/chrome/browser/extensions/extension.h +++ b/chrome/browser/extensions/extension.h @@ -35,6 +35,7 @@ class Extension { static const wchar_t* kFormatVersionKey; static const wchar_t* kIdKey; static const wchar_t* kJsKey; + static const wchar_t* kCssKey; static const wchar_t* kMatchesKey; static const wchar_t* kNameKey; static const wchar_t* kRunAtKey; @@ -51,6 +52,8 @@ class Extension { // Error messages returned from InitFromValue(). static const char* kInvalidContentScriptError; static const char* kInvalidContentScriptsListError; + static const char* kInvalidCssError; + static const char* kInvalidCssListError; static const char* kInvalidDescriptionError; static const char* kInvalidFormatVersionError; static const char* kInvalidIdError; @@ -62,11 +65,12 @@ class Extension { static const char* kInvalidMatchError; static const char* kInvalidMatchesError; static const char* kInvalidNameError; + static const char* kInvalidPluginsDirError; static const char* kInvalidRunAtError; + static const char* kInvalidToolstripError; static const char* kInvalidVersionError; static const char* kInvalidZipHashError; - static const char* kInvalidPluginsDirError; - static const char* kInvalidToolstripError; + static const char* kMissingFileError; // The number of bytes in a legal id. static const size_t kIdSize; @@ -78,6 +82,9 @@ class Extension { // NOTE: Static so that it can be used from multiple threads. static GURL GetResourceURL(const GURL& extension_url, const std::string& relative_path); + GURL GetResourceURL(const std::string& relative_path) { + return GetResourceURL(url(), relative_path); + } // Returns an absolute path to a resource inside of an extension. The // |extension_path| argument should be the path() from an Extension object. @@ -86,6 +93,9 @@ class Extension { // NOTE: Static so that it can be used from multiple threads. static FilePath GetResourcePath(const FilePath& extension_path, const std::string& relative_path); + FilePath GetResourcePath(const std::string& relative_path) { + return GetResourcePath(path(), relative_path); + } // Initialize the extension from a parsed manifest. bool InitFromValue(const DictionaryValue& value, std::string* error); @@ -110,6 +120,12 @@ class Extension { const GURL& toolstrip_url() const { return toolstrip_url_; } private: + // Helper method that loads a UserScript object from a + // dictionary in the content_script list of the manifest. + bool LoadUserScriptHelper(const DictionaryValue* content_script, + int definition_index, + std::string* error, + UserScript* result); // The absolute path to the directory the extension is stored in. FilePath path_; diff --git a/chrome/browser/extensions/extension_unittest.cc b/chrome/browser/extensions/extension_unittest.cc index aa8d791..646a0f4 100644 --- a/chrome/browser/extensions/extension_unittest.cc +++ b/chrome/browser/extensions/extension_unittest.cc @@ -134,17 +134,24 @@ TEST(ExtensionTest, InitFromValueInvalid) { input_value->GetList(Extension::kContentScriptsKey, &content_scripts); content_scripts->GetDictionary(0, &user_script); user_script->Remove(Extension::kJsKey, NULL); + user_script->Remove(Extension::kCssKey, NULL); EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); - EXPECT_TRUE(MatchPattern(error, Extension::kInvalidJsListError)); + EXPECT_TRUE(MatchPattern(error, Extension::kMissingFileError)); user_script->Set(Extension::kJsKey, Value::CreateIntegerValue(42)); EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); EXPECT_TRUE(MatchPattern(error, Extension::kInvalidJsListError)); + user_script->Set(Extension::kCssKey, new ListValue); + user_script->Set(Extension::kJsKey, new ListValue); + EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); + EXPECT_TRUE(MatchPattern(error, Extension::kMissingFileError)); + user_script->Remove(Extension::kCssKey, NULL); + ListValue* files = new ListValue; user_script->Set(Extension::kJsKey, files); EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); - EXPECT_TRUE(MatchPattern(error, Extension::kInvalidJsCountError)); + EXPECT_TRUE(MatchPattern(error, Extension::kMissingFileError)); // Test invalid file element files->Set(0, Value::CreateIntegerValue(42)); @@ -156,6 +163,20 @@ TEST(ExtensionTest, InitFromValueInvalid) { files->Set(1, Value::CreateStringValue("bar.js")); EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); EXPECT_TRUE(MatchPattern(error, Extension::kInvalidJsCountError)); + + // Remove all script files + user_script->Remove(Extension::kJsKey, NULL); + // Test the css element + user_script->Set(Extension::kCssKey, Value::CreateIntegerValue(42)); + EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); + EXPECT_TRUE(MatchPattern(error, Extension::kInvalidCssListError)); + + // Test invalid file element + ListValue* css_files = new ListValue; + user_script->Set(Extension::kCssKey, css_files); + css_files->Set(0, Value::CreateIntegerValue(42)); + EXPECT_FALSE(extension.InitFromValue(*input_value, &error)); + EXPECT_TRUE(MatchPattern(error, Extension::kInvalidCssError)); } TEST(ExtensionTest, InitFromValueValid) { diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 1b66bea..2c3451b 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -298,14 +298,27 @@ Extension* ExtensionsServiceBackend::LoadExtension( } // Validate that claimed resources actually exist. - for (UserScriptList::const_iterator iter = - extension->content_scripts().begin(); - iter != extension->content_scripts().end(); ++iter) { - if (!file_util::PathExists(iter->path())) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load content script '%s'.", - WideToUTF8(iter->path().ToWStringHack()).c_str())); - return NULL; + for (size_t i = 0; i < extension->content_scripts().size(); ++i) { + const UserScript& script = extension->content_scripts()[i]; + + for (size_t j = 0; j < script.js_scripts().size(); j++) { + const FilePath& path = script.js_scripts()[j].path(); + if (!file_util::PathExists(path)) { + ReportExtensionLoadError(extension_path, + StringPrintf("Could not load '%s' for content script.", + WideToUTF8(path.ToWStringHack()).c_str())); + return NULL; + } + } + + for (size_t j = 0; j < script.css_scripts().size(); j++) { + const FilePath& path = script.css_scripts()[j].path(); + if (!file_util::PathExists(path)) { + ReportExtensionLoadError(extension_path, + StringPrintf("Could not load '%s' for content script.", + WideToUTF8(path.ToWStringHack()).c_str())); + return NULL; + } } } diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index 304aba4..493551c3 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -176,15 +176,15 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { EXPECT_EQ("https://*.google.com/*", scripts[0].url_patterns()[1].GetAsString()); EXPECT_EQ(extension->path().AppendASCII("script1.js").value(), - scripts[0].path().value()); + scripts[0].js_scripts()[0].path().value()); EXPECT_EQ(1u, scripts[1].url_patterns().size()); EXPECT_EQ("http://*.yahoo.com/*", scripts[1].url_patterns()[0].GetAsString()); EXPECT_EQ(extension->path().AppendASCII("script2.js").value(), - scripts[1].path().value()); + scripts[1].js_scripts()[0].path().value()); EXPECT_EQ(1u, scripts[2].url_patterns().size()); EXPECT_EQ("http://*.news.com/*", scripts[2].url_patterns()[0].GetAsString()); EXPECT_EQ(extension->path().AppendASCII("js_files").AppendASCII("script3.js") - .value(), scripts[2].path().value()); + .value(), scripts[2].js_scripts()[0].path().value()); EXPECT_EQ(std::string("10123456789abcdef0123456789abcdef0123456"), frontend->extensions()->at(1)->id()); @@ -231,7 +231,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectoryFail) { EXPECT_TRUE(MatchPattern(GetErrors()[1], std::string("Could not load extension from '*'. ") + - Extension::kInvalidJsListError)) << GetErrors()[1]; + Extension::kMissingFileError)) << GetErrors()[1]; EXPECT_TRUE(MatchPattern(GetErrors()[2], std::string("Could not load extension from '*'. ") + diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc index 3048172..ee273dc 100644 --- a/chrome/browser/extensions/extensions_ui.cc +++ b/chrome/browser/extensions/extensions_ui.cc @@ -85,23 +85,35 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { dom_ui_->CallJavascriptFunction(L"returnExtensionsData", results); } +static void CreateScriptFileDetailValue( + const FilePath& extension_path, const UserScript::FileList& scripts, + const wchar_t* key, DictionaryValue* script_data) { + if (scripts.empty()) + return; + + ListValue *list = new ListValue(); + for (size_t i = 0; i < scripts.size(); ++i) { + const UserScript::File &file = scripts[i]; + // We are passing through GURLs to canonicalize the output to a valid + // URL path fragment. + GURL script_url = net::FilePathToFileURL(file.path()); + GURL extension_url = net::FilePathToFileURL(extension_path); + std::string relative_path = + script_url.spec().substr(extension_url.spec().length() + 1); + + list->Append(new StringValue(relative_path)); + } + script_data->Set(key, list); +} + // Static DictionaryValue* ExtensionsDOMHandler::CreateContentScriptDetailValue( const UserScript& script, const FilePath& extension_path) { DictionaryValue* script_data = new DictionaryValue(); - - // TODO(rafaelw): When UserScript supports multiple js, this will have to - // put them all in this list; - ListValue *js_list = new ListValue(); - // We are passing through GURLs to canonicalize the output to a valid - // URL path fragment. - GURL script_url = net::FilePathToFileURL(script.path()); - GURL extension_url = net::FilePathToFileURL(extension_path); - std::string relative_js_path = - script_url.spec().substr(extension_url.spec().length() + 1); - - js_list->Append(new StringValue(relative_js_path)); - script_data->Set(L"js", js_list); + CreateScriptFileDetailValue(extension_path, script.js_scripts(), L"js", + script_data); + CreateScriptFileDetailValue(extension_path, script.css_scripts(), L"css", + script_data); // Get list of glob "matches" strings ListValue *url_pattern_list = new ListValue(); diff --git a/chrome/browser/extensions/user_script_master.cc b/chrome/browser/extensions/user_script_master.cc index 2f50371..bf0ba7e 100644 --- a/chrome/browser/extensions/user_script_master.cc +++ b/chrome/browser/extensions/user_script_master.cc @@ -16,9 +16,11 @@ #include "chrome/common/url_constants.h" #include "net/base/net_util.h" -// static -bool GetDeclarationValue(const StringPiece& line, const StringPiece& prefix, - std::string* value) { + +// Helper function to parse greasesmonkey headers +static bool GetDeclarationValue(const StringPiece& line, + const StringPiece& prefix, + std::string* value) { if (!line.starts_with(prefix)) return false; @@ -126,20 +128,17 @@ void UserScriptMaster::ScriptReloader::NotifyMaster( Release(); } -void UserScriptMaster::ScriptReloader::RunScan( - const FilePath script_dir, const UserScriptList lone_scripts) { - base::SharedMemory* shared_memory = GetNewScripts(script_dir, lone_scripts); - - // Post the new scripts back to the master's message loop. - master_message_loop_->PostTask(FROM_HERE, - NewRunnableMethod(this, - &UserScriptMaster::ScriptReloader::NotifyMaster, - shared_memory)); +static void LoadScriptContent(UserScript::File* script_file) { + std::string content; + CHECK(file_util::ReadFileToString(script_file->path(), &content)) << + "Failed to read script content: " << script_file->path().value(); + script_file->set_content(content); } -base::SharedMemory* UserScriptMaster::ScriptReloader::GetNewScripts( - const FilePath& script_dir, const UserScriptList& lone_scripts) { - UserScriptList all_scripts; +void UserScriptMaster::ScriptReloader::LoadScriptsFromDirectory( + const FilePath script_dir, UserScriptList* result) { + // Clear the list. We will populate it with the scrips found in script_dir. + result->clear(); // Find all the scripts in |script_dir|. if (!script_dir.value().empty()) { @@ -152,42 +151,55 @@ base::SharedMemory* UserScriptMaster::ScriptReloader::GetNewScripts( FILE_PATH_LITERAL("*.user.js")); for (FilePath file = enumerator.Next(); !file.value().empty(); file = enumerator.Next()) { - all_scripts.push_back(UserScript()); - UserScript& info = all_scripts.back(); - info.set_url(GURL(std::string(chrome::kUserScriptScheme) + ":/" + - net::FilePathToFileURL(file.ToWStringHack()).ExtractFileName())); - info.set_path(file); + result->push_back(UserScript()); + UserScript& user_script = result->back(); + // Push single js file in this UserScript. + GURL url(std::string(chrome::kUserScriptScheme) + ":/" + + net::FilePathToFileURL(file.ToWStringHack()).ExtractFileName()); + user_script.js_scripts().push_back(UserScript::File(file, url)); + UserScript::File& script_file = user_script.js_scripts().back(); + LoadScriptContent(&script_file); + ParseMetadataHeader(script_file.GetContent(), &user_script); } } +} - if (all_scripts.empty() && lone_scripts.empty()) - return NULL; - - // Add all the lone scripts. - all_scripts.insert(all_scripts.end(), lone_scripts.begin(), - lone_scripts.end()); - - // Load and pickle each script. Look for a metadata header if there are no - // url_patterns specified already. - Pickle pickle; - pickle.WriteSize(all_scripts.size()); - for (UserScriptList::iterator iter = all_scripts.begin(); - iter != all_scripts.end(); ++iter) { - // TODO(aa): Support unicode script files. - std::string contents; - file_util::ReadFileToString(iter->path().ToWStringHack(), &contents); - - if (iter->url_patterns().empty()) { - // TODO(aa): Handle errors parsing header. - if (!ParseMetadataHeader(contents, &(*iter))) - return NULL; +static void LoadLoneScripts(UserScriptList lone_script) { + for (size_t i = 0; i < lone_script.size(); ++i) { + for (size_t k = 0; k < lone_script[i].js_scripts().size(); ++k) { + UserScript::File& script_file = lone_script[i].js_scripts()[k]; + if (script_file.GetContent().empty()) { + LoadScriptContent(&script_file); + } } + for (size_t k = 0; k < lone_script[i].css_scripts().size(); ++k) { + UserScript::File& script_file = lone_script[i].css_scripts()[k]; + if (script_file.GetContent().empty()) { + LoadScriptContent(&script_file); + } + } + } +} - iter->Pickle(&pickle); - +// Pickle user scripts and return pointer to the shared memory. +static base::SharedMemory* Serialize(UserScriptList& scripts) { + if (scripts.empty()) + return NULL; // Nothing to serialize + Pickle pickle; + pickle.WriteSize(scripts.size()); + for (size_t i = 0; i < scripts.size(); i++) { + UserScript& script = scripts[i]; + script.Pickle(&pickle); // Write scripts as 'data' so that we can read it out in the slave without // allocating a new string. - pickle.WriteData(contents.c_str(), contents.length()); + for (size_t j = 0; j < script.js_scripts().size(); j++) { + StringPiece contents = script.js_scripts()[j].GetContent(); + pickle.WriteData(contents.data(), contents.length()); + } + for (size_t j = 0; j < script.css_scripts().size(); j++) { + StringPiece contents = script.css_scripts()[j].GetContent(); + pickle.WriteData(contents.data(), contents.length()); + } } // Create the shared memory object. @@ -210,6 +222,31 @@ base::SharedMemory* UserScriptMaster::ScriptReloader::GetNewScripts( return shared_memory.release(); } +// This method will be called from the file thread +void UserScriptMaster::ScriptReloader::RunScan( + const FilePath script_dir, UserScriptList lone_script) { + UserScriptList scripts; + // Get list of user scripts. + if (!script_dir.empty()) + LoadScriptsFromDirectory(script_dir, &scripts); + + LoadLoneScripts(lone_script); + + // Merge with the explicit scripts + scripts.reserve(scripts.size() + lone_script.size()); + scripts.insert(scripts.end(), + lone_script.begin(), lone_script.end()); + + // Scripts now contains list of up-to-date scripts. Load the content in the + // shared memory and let the master know it's ready. We need to post the task + // back even if no scripts ware found to balance the AddRef/Release calls + master_message_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, + &ScriptReloader::NotifyMaster, + Serialize(scripts))); +} + + UserScriptMaster::UserScriptMaster(MessageLoop* worker_loop, const FilePath& script_dir) : user_script_dir_(script_dir), diff --git a/chrome/browser/extensions/user_script_master.h b/chrome/browser/extensions/user_script_master.h index f156180..d546b62 100644 --- a/chrome/browser/extensions/user_script_master.h +++ b/chrome/browser/extensions/user_script_master.h @@ -74,6 +74,9 @@ class UserScriptMaster : public base::RefCounted<UserScriptMaster>, static bool ParseMetadataHeader(const StringPiece& script_text, UserScript* script); + static void LoadScriptsFromDirectory(const FilePath script_dir, + UserScriptList* result); + ScriptReloader(UserScriptMaster* master) : master_(master), master_message_loop_(MessageLoop::current()) {} @@ -91,8 +94,8 @@ class UserScriptMaster : public base::RefCounted<UserScriptMaster>, // Where functions are run: // master file // StartScan -> RunScan - // GetNewScripts() - // ParseMetadataHeader() + // LoadScriptsFromDirectory() + // LoadLoneScripts() // NotifyMaster <- RunScan // Runs on the master thread. @@ -103,13 +106,7 @@ class UserScriptMaster : public base::RefCounted<UserScriptMaster>, // Scan the specified directory and lone scripts, calling NotifyMaster when // done. The parameters are intentionally passed by value so their lifetimes // aren't tied to the caller. - void RunScan(const FilePath script_dir, const UserScriptList lone_scripts); - - // Runs on the File thread. - // Scan the script directory and lone scripts, returning either a new - // SharedMemory or NULL on error. - base::SharedMemory* GetNewScripts(const FilePath& script_dir, - const UserScriptList& lone_scripts); + void RunScan(const FilePath script_dir, UserScriptList lone_scripts); // A pointer back to our master. // May be NULL if DisownMaster() is called. diff --git a/chrome/common/extensions/user_script.cc b/chrome/common/extensions/user_script.cc index 9a0da2d..b970570 100644 --- a/chrome/common/extensions/user_script.cc +++ b/chrome/common/extensions/user_script.cc @@ -7,7 +7,7 @@ #include "base/string_util.h" bool UserScript::MatchesUrl(const GURL& url) { - for (std::vector<std::string>::iterator glob = globs_.begin(); + for (std::vector<std::string>::const_iterator glob = globs_.begin(); glob != globs_.end(); ++glob) { if (MatchPattern(url.spec(), *glob)) return true; @@ -22,35 +22,60 @@ bool UserScript::MatchesUrl(const GURL& url) { return false; } -void UserScript::Pickle(::Pickle* pickle) { +void UserScript::File::Pickle(::Pickle* pickle) const { pickle->WriteString(url_.spec()); - pickle->WriteInt(run_location_); + // Do not write path. It's not needed in the renderer. + // Do not write content. It will be serialized by other means. +} + +void UserScript::File::Unpickle(const ::Pickle& pickle, void** iter) { + // Read url. + std::string url; + CHECK(pickle.ReadString(iter, &url)); + set_url(GURL(url)); +} - // Don't write path as we don't need that in the renderer. +void UserScript::Pickle(::Pickle* pickle) const { + // Write the run location. + pickle->WriteInt(run_location()); + // Write globs. pickle->WriteSize(globs_.size()); - for (std::vector<std::string>::iterator glob = globs_.begin(); + for (std::vector<std::string>::const_iterator glob = globs_.begin(); glob != globs_.end(); ++glob) { pickle->WriteString(*glob); } + // Write url patterns. pickle->WriteSize(url_patterns_.size()); - for (std::vector<URLPattern>::iterator pattern = url_patterns_.begin(); + for (std::vector<URLPattern>::const_iterator pattern = url_patterns_.begin(); pattern != url_patterns_.end(); ++pattern) { pickle->WriteString(pattern->GetAsString()); } + + // Write js scripts. + pickle->WriteSize(js_scripts_.size()); + for (FileList::const_iterator file = js_scripts_.begin(); + file != js_scripts_.end(); ++file) { + file->Pickle(pickle); + } + + // Write css scripts. + pickle->WriteSize(css_scripts_.size()); + for (FileList::const_iterator file = css_scripts_.begin(); + file != css_scripts_.end(); ++file) { + file->Pickle(pickle); + } } void UserScript::Unpickle(const ::Pickle& pickle, void** iter) { - std::string url_spec; - CHECK(pickle.ReadString(iter, &url_spec)); - url_ = GURL(url_spec); - + // Read the run location. int run_location = 0; CHECK(pickle.ReadInt(iter, &run_location)); - CHECK(run_location >= 0 && run_location < UserScript::RUN_LOCATION_LAST); - run_location_ = static_cast<UserScript::RunLocation>(run_location); + CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST); + run_location_ = static_cast<RunLocation>(run_location); + // Read globs. size_t num_globs = 0; CHECK(pickle.ReadSize(iter, &num_globs)); @@ -61,6 +86,7 @@ void UserScript::Unpickle(const ::Pickle& pickle, void** iter) { globs_.push_back(glob); } + // Read url patterns. size_t num_patterns = 0; CHECK(pickle.ReadSize(iter, &num_patterns)); @@ -72,4 +98,24 @@ void UserScript::Unpickle(const ::Pickle& pickle, void** iter) { CHECK(pattern.Parse(pattern_str)); url_patterns_.push_back(pattern); } + + // Read js scripts. + size_t num_js_files = 0; + CHECK(pickle.ReadSize(iter, &num_js_files)); + js_scripts_.clear(); + for (size_t i = 0; i < num_js_files; ++i) { + File file; + file.Unpickle(pickle, iter); + js_scripts_.push_back(file); + } + + // Read css scripts. + size_t num_css_files = 0; + CHECK(pickle.ReadSize(iter, &num_css_files)); + css_scripts_.clear(); + for (size_t i = 0; i < num_css_files; ++i) { + File file; + file.Unpickle(pickle, iter); + css_scripts_.push_back(file); + } } diff --git a/chrome/common/extensions/user_script.h b/chrome/common/extensions/user_script.h index 671f158..bf1ec0d 100644 --- a/chrome/common/extensions/user_script.h +++ b/chrome/common/extensions/user_script.h @@ -10,6 +10,7 @@ #include "base/file_path.h" #include "base/pickle.h" +#include "base/string_piece.h" #include "chrome/common/extensions/url_pattern.h" #include "googleurl/src/gurl.h" @@ -27,18 +28,62 @@ class UserScript { RUN_LOCATION_LAST // Leave this as the last item. }; + // Holds actual script file info. + class File { + public: + File(const FilePath& path, const GURL& url): + path_(path), + url_(url) { + } + File() {} + + const FilePath& path() const { return path_; } + void set_path(const FilePath& path) { path_ = path; } + + const GURL& url() const { return url_; } + void set_url(const GURL& url) { url_ = url; } + + // If external_content_ is set returns it as content otherwise it returns + // content_ + const StringPiece GetContent() const { + if (external_content_.data()) + return external_content_; + else + return content_; + } + void set_external_content(const StringPiece& content) { + external_content_ = content; + } + const void set_content(const StringPiece& content) { + content_.assign(content.begin(), content.end()); + } + + // Serialization support. The content and path_ member will not be + // serialized! + void Pickle(::Pickle* pickle) const; + void Unpickle(const ::Pickle& pickle, void** iter); + + private: + // Where is the script file lives on the disk. + FilePath path_; + + // The url to this scipt file. + GURL url_; + + // The script content. It can be set to either loaded_content_ or + // externally allocated string. + StringPiece external_content_; + + // Set when the content is loaded by LoadContent + std::string content_; + }; + + typedef std::vector<File> FileList; + // Constructor. Default the run location to document end, which is like // Greasemonkey and probably more useful for typical scripts. UserScript() : run_location_(DOCUMENT_END) {} - // The URL to retrieve the content of this script at. - const GURL& url() const { return url_; } - void set_url(const GURL& url) { url_ = url; } - - // The path to find the script at. - const FilePath& path() const { return path_; } - void set_path(const FilePath& path) { path_ = path; } - // 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; } @@ -57,12 +102,21 @@ class UserScript { } void clear_url_patterns() { url_patterns_.clear(); } + // List of js scripts for this user script + FileList& js_scripts() { return js_scripts_; } + const FileList& js_scripts() const { return js_scripts_; } + + // List of css scripts for this user script + FileList& css_scripts() { return css_scripts_; } + const FileList& css_scripts() const { return css_scripts_; } + // Returns true if the script should be applied to the specified URL, false // otherwise. bool MatchesUrl(const GURL& url); - // Serialize the script into a pickle. - void Pickle(::Pickle* pickle); + // Serialize the UserScript into a pickle. The content of the scripts and + // paths to UserScript::Files will not be serialized! + void Pickle(::Pickle* pickle) const; // Deserialize the script from a pickle. Note that this always succeeds // because presumably we were the one that pickled it, and we did it @@ -70,12 +124,6 @@ class UserScript { void Unpickle(const ::Pickle& pickle, void** iter); private: - // The URL to the content of the script. - GURL url_; - - // The path to the content of the script. - FilePath path_; - // The location to run the script inside the document. RunLocation run_location_; @@ -86,6 +134,12 @@ class UserScript { // URLPatterns that determine pages to inject the script into. These are // only used with scripts that are part of extensions. std::vector<URLPattern> url_patterns_; + + // List of js scripts defined in content_scripts + FileList js_scripts_; + + // List of css scripts defined in content_scripts + FileList css_scripts_; }; typedef std::vector<UserScript> UserScriptList; diff --git a/chrome/common/extensions/user_script_unittest.cc b/chrome/common/extensions/user_script_unittest.cc index 6230497..46014f8 100644 --- a/chrome/common/extensions/user_script_unittest.cc +++ b/chrome/common/extensions/user_script_unittest.cc @@ -76,8 +76,17 @@ TEST(UserScriptTest, Pickle) { ASSERT_TRUE(pattern2.Parse("http://bar/baz*")); UserScript script1; - script1.set_url(GURL("chrome-user-script:/foo.user.js")); + script1.js_scripts().push_back(UserScript::File( + FilePath(FILE_PATH_LITERAL("c:\\foo\\foo.user.js")), + GURL("chrome-user-script:/foo.user.js"))); + script1.css_scripts().push_back(UserScript::File( + FilePath(FILE_PATH_LITERAL("c:\\foo\\foo.user.css")), + GURL("chrome-user-script:/foo.user.css"))); + script1.css_scripts().push_back(UserScript::File( + FilePath(FILE_PATH_LITERAL("c:\\foo\\foo2.user.css")), + GURL("chrome-user-script:/foo2.user.css"))); script1.set_run_location(UserScript::DOCUMENT_START); + script1.add_url_pattern(pattern1); script1.add_url_pattern(pattern2); @@ -88,7 +97,14 @@ TEST(UserScriptTest, Pickle) { UserScript script2; script2.Unpickle(pickle, &iter); - EXPECT_EQ(script1.url(), script2.url()); + EXPECT_EQ(1U, script2.js_scripts().size()); + EXPECT_EQ(script1.js_scripts()[0].url(), script2.js_scripts()[0].url()); + + EXPECT_EQ(2U, script2.css_scripts().size()); + for (size_t i = 0; i < script2.js_scripts().size(); ++i) { + EXPECT_EQ(script1.css_scripts()[i].url(), script2.css_scripts()[i].url()); + } + ASSERT_EQ(script1.globs().size(), script2.globs().size()); for (size_t i = 0; i < script1.globs().size(); ++i) { EXPECT_EQ(script1.globs()[i], script2.globs()[i]); diff --git a/chrome/renderer/user_script_slave.cc b/chrome/renderer/user_script_slave.cc index 6362109..04957ad 100644 --- a/chrome/renderer/user_script_slave.cc +++ b/chrome/renderer/user_script_slave.cc @@ -48,7 +48,6 @@ UserScriptSlave::UserScriptSlave() bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { scripts_.clear(); - script_contents_.clear(); // Create the shared memory object (read only). shared_memory_.reset(new base::SharedMemory(shared_memory, true)); @@ -74,19 +73,27 @@ bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { pickle_size); pickle.ReadSize(&iter, &num_scripts); + scripts_.reserve(num_scripts); for (size_t i = 0; i < num_scripts; ++i) { - UserScript* script = new UserScript(); + scripts_.push_back(new UserScript()); + UserScript* script = scripts_.back(); script->Unpickle(pickle, &iter); // Note that this is a pointer into shared memory. We don't own it. It gets // cleared up when the last renderer or browser process drops their // reference to the shared memory. - const char* body = NULL; - int body_length = 0; - CHECK(pickle.ReadData(&iter, &body, &body_length)); - - scripts_.push_back(script); - script_contents_[script] = StringPiece(body, body_length); + for (size_t j = 0; j < script->js_scripts().size(); ++j) { + const char* body = NULL; + int body_length = 0; + CHECK(pickle.ReadData(&iter, &body, &body_length)); + script->js_scripts()[j].set_external_content(body); + } + for (size_t j = 0; j < script->css_scripts().size(); ++j) { + const char* body = NULL; + int body_length = 0; + CHECK(pickle.ReadData(&iter, &body, &body_length)); + script->css_scripts()[j].set_external_content(body); + } } return true; @@ -97,21 +104,36 @@ bool UserScriptSlave::InjectScripts(WebFrame* frame, PerfTimer timer; int num_matched = 0; - for (std::vector<UserScript*>::iterator script = scripts_.begin(); - script != scripts_.end(); ++script) { - if ((*script)->MatchesUrl(frame->GetURL()) && - (*script)->run_location() == location) { - webkit_glue::WebScriptSource sources[] = { - webkit_glue::WebScriptSource(api_js_.as_string()), - webkit_glue::WebScriptSource( - script_contents_[*script].as_string(), (*script)->url()) - }; - - frame->ExecuteScriptInNewContext(sources, arraysize(sources)); - ++num_matched; + std::vector<webkit_glue::WebScriptSource> sources; + for (size_t i = 0; i < scripts_.size(); ++i) { + UserScript* script = scripts_[i]; + if (!script->MatchesUrl(frame->GetURL())) + continue; // This frame doesn't match the script url pattern, skip it. + + ++num_matched; + // CSS files are always injected on document start before js scripts. + if (location == UserScript::DOCUMENT_START) { + for (size_t j = 0; j < script->css_scripts().size(); ++j) { + UserScript::File& file = script->css_scripts()[j]; + frame->InsertCSSStyles(file.GetContent().as_string()); + } + } + if (script->run_location() == location) { + for (size_t j = 0; j < script->js_scripts().size(); ++j) { + UserScript::File &file = script->js_scripts()[j]; + sources.push_back(webkit_glue::WebScriptSource( + file.GetContent().as_string(), file.url())); + } } } + if (!sources.empty()) { + sources.insert(sources.begin(), + webkit_glue::WebScriptSource(api_js_.as_string())); + frame->ExecuteScriptInNewContext(&sources.front(), sources.size()); + } + + // Log debug info. if (location == UserScript::DOCUMENT_START) { HISTOGRAM_COUNTS_100("UserScripts:DocStart:Count", num_matched); HISTOGRAM_TIMES("UserScripts:DocStart:Time", timer.Elapsed()); @@ -120,8 +142,7 @@ bool UserScriptSlave::InjectScripts(WebFrame* frame, HISTOGRAM_TIMES("UserScripts:DocEnd:Time", timer.Elapsed()); } - LOG(INFO) << "Injected " << num_matched << " scripts into " << + LOG(INFO) << "Injected " << num_matched << " user scripts into " << frame->GetURL().spec(); - return true; } diff --git a/chrome/renderer/user_script_slave.h b/chrome/renderer/user_script_slave.h index a3363c1..b8b80a1 100644 --- a/chrome/renderer/user_script_slave.h +++ b/chrome/renderer/user_script_slave.h @@ -37,9 +37,6 @@ class UserScriptSlave { std::vector<UserScript*> scripts_; STLElementDeleter<std::vector<UserScript*> > script_deleter_; - // Script contents. - std::map<UserScript*, StringPiece> script_contents_; - // Greasemonkey API source that is injected with the scripts. StringPiece api_js_; diff --git a/chrome/test/data/extensions/good/extension1/1/manifest.json b/chrome/test/data/extensions/good/extension1/1/manifest.json index 77a4639..e64b1d0 100644 --- a/chrome/test/data/extensions/good/extension1/1/manifest.json +++ b/chrome/test/data/extensions/good/extension1/1/manifest.json @@ -8,7 +8,8 @@ "content_scripts": [ { "matches": ["http://*.google.com/*", "https://*.google.com/*"], - "js": ["script1.js"] + "js": ["script1.js"], + "css": ["style1.css", "style2.css", "style2.css"] }, { "matches": ["http://*.yahoo.com/*"], diff --git a/chrome/test/data/extensions/good/extension1/1/style1.css b/chrome/test/data/extensions/good/extension1/1/style1.css new file mode 100644 index 0000000..2676c97 --- /dev/null +++ b/chrome/test/data/extensions/good/extension1/1/style1.css @@ -0,0 +1,2 @@ +* {background-color: beige !important}
+p {background-color: rgb(128,128,128)}
diff --git a/chrome/test/data/extensions/good/extension1/1/style2.css b/chrome/test/data/extensions/good/extension1/1/style2.css new file mode 100644 index 0000000..c851a8f --- /dev/null +++ b/chrome/test/data/extensions/good/extension1/1/style2.css @@ -0,0 +1,6 @@ +h1 {color: white; background-color: black;}
+h2 {color: white; background-color: black;}
+h3 {color: white; background-color: black;}
+h4 {color: white; background-color: black;}
+h5 {color: white; background-color: black;}
+h6 {color: white; background-color: black;}
diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json index 35b8a11..e4e4b4d 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json @@ -5,7 +5,8 @@ "content_scripts": [ { "matches": ["http://*.google.com/*", "https://*.google.com/*"], - "js": ["script1.js"] + "js": ["script1.js"], + "css": ["style1.css", "style2.css", "style2.css"] }, { "matches": ["http://*.yahoo.com/*"], diff --git a/webkit/glue/webframe.h b/webkit/glue/webframe.h index 90b1ec8..9be5100 100644 --- a/webkit/glue/webframe.h +++ b/webkit/glue/webframe.h @@ -99,6 +99,9 @@ class WebFrame { virtual void ExecuteScriptInNewContext( const webkit_glue::WebScriptSource* sources, int num_sources) = 0; + // Inserts the given CSS styles at the beginning of the document. + virtual bool InsertCSSStyles(const std::string& css) = 0; + // Returns a string representing the state of the previous page load for // later use when loading. The previous page is the page that was loaded // before DidCommitLoadForFrame was received. diff --git a/webkit/glue/webframe_impl.cc b/webkit/glue/webframe_impl.cc index 9f87b20..5ad4219 100644 --- a/webkit/glue/webframe_impl.cc +++ b/webkit/glue/webframe_impl.cc @@ -93,6 +93,7 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "GraphicsContext.h" #include "HTMLHeadElement.h" #include "HTMLLinkElement.h" +#include "HTMLNames.h" #include "HistoryItem.h" #include "InspectorController.h" #include "markup.h" @@ -1656,6 +1657,25 @@ void WebFrameImpl::ExecuteScript(const webkit_glue::WebScriptSource& source) { source.start_line)); } +bool WebFrameImpl::InsertCSSStyles(const std::string& css) { + Document* document = frame()->document(); + if (!document) + return false; + WebCore::Element* document_element = document->documentElement(); + if (!document_element) + return false; + + RefPtr<WebCore::Element> stylesheet = document->createElement( + WebCore::HTMLNames::styleTag, false); + ExceptionCode err = 0; + stylesheet->setTextContent(webkit_glue::StdStringToString(css), err); + DCHECK(!err) << "Failed to set style element content"; + WebCore::Node* first = document_element->firstChild(); + bool success = document_element->insertBefore(stylesheet, first, err); + DCHECK(success) << "Failed to insert stylesheet"; + return success; +} + void WebFrameImpl::ExecuteScriptInNewContext( const webkit_glue::WebScriptSource* sources_in, int num_sources) { Vector<WebCore::ScriptSourceCode> sources; diff --git a/webkit/glue/webframe_impl.h b/webkit/glue/webframe_impl.h index c0b4616..96045e1 100644 --- a/webkit/glue/webframe_impl.h +++ b/webkit/glue/webframe_impl.h @@ -93,6 +93,7 @@ class WebFrameImpl : public WebFrame, public base::RefCounted<WebFrameImpl> { virtual void ExecuteScript(const webkit_glue::WebScriptSource& source); virtual void ExecuteScriptInNewContext( const webkit_glue::WebScriptSource* sources, int num_sources); + virtual bool InsertCSSStyles(const std::string& css); virtual bool GetPreviousHistoryState(std::string* history_state) const; virtual bool GetCurrentHistoryState(std::string* history_state) const; virtual bool HasCurrentHistoryState() const; |