forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
aa@chromium.org
committed
Nov 4, 2009
1 parent
6c856ae
commit 6657afa
Showing
27 changed files
with
676 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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))); | ||
} |
Oops, something went wrong.