summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-07 10:26:31 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-07 10:26:31 +0000
commitb9a622b9fe09868974a5af845bc474752b239566 (patch)
tree939d1e3947e35bbd960a925992bc7f049fe5775f /chrome
parent72cbd32707a2ede460bcc1b3cb199e653282a8ed (diff)
downloadchromium_src-b9a622b9fe09868974a5af845bc474752b239566.zip
chromium_src-b9a622b9fe09868974a5af845bc474752b239566.tar.gz
chromium_src-b9a622b9fe09868974a5af845bc474752b239566.tar.bz2
Revert "Implement chromium.self in content scripts..."
This reverts commit 61ab30f52667e739602ab2af4fd8f2d8a0a2a2f0. Still seeing memory errors. Review URL: http://codereview.chromium.org/63056 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13243 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/extension.cc36
-rw-r--r--chrome/browser/extensions/extension.h8
-rwxr-xr-xchrome/browser/extensions/extension_content_script_inject_unittest.cc62
-rw-r--r--chrome/browser/extensions/extension_ui_unittest.cc2
-rw-r--r--chrome/browser/extensions/extension_unittest.cc61
-rwxr-xr-xchrome/browser/extensions/extension_view_unittest.cc63
-rw-r--r--chrome/browser/extensions/extensions_service.cc24
-rw-r--r--chrome/browser/extensions/extensions_service.h2
-rwxr-xr-xchrome/browser/extensions/test_extension_loader.cc63
-rwxr-xr-xchrome/browser/extensions/test_extension_loader.h35
-rw-r--r--chrome/chrome.gyp4
-rw-r--r--chrome/common/extensions/user_script.cc6
-rw-r--r--chrome/common/extensions/user_script.h9
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js6
-rw-r--r--chrome/renderer/user_script_slave.cc35
-rwxr-xr-xchrome/test/data/extensions/content_script_inject/js_test.js33
-rwxr-xr-xchrome/test/data/extensions/content_script_inject/manifest.json21
-rw-r--r--chrome/test/data/extensions/content_script_inject/script1.css10
-rwxr-xr-xchrome/test/data/extensions/content_script_inject/script1a.js7
-rwxr-xr-xchrome/test/data/extensions/content_script_inject/script1b.js27
-rwxr-xr-xchrome/test/data/extensions/content_script_inject/script2.js12
-rw-r--r--chrome/test/data/extensions/content_script_inject/script3.js31
-rwxr-xr-xchrome/test/data/extensions/content_script_inject_page.html10
-rw-r--r--chrome/test/unit/unittests.vcproj12
24 files changed, 132 insertions, 447 deletions
diff --git a/chrome/browser/extensions/extension.cc b/chrome/browser/extensions/extension.cc
index 1aeaeaa..d8ab403 100644
--- a/chrome/browser/extensions/extension.cc
+++ b/chrome/browser/extensions/extension.cc
@@ -103,6 +103,21 @@ Extension::Extension(const Extension& rhs)
theme_paths_(rhs.theme_paths_) {
}
+const GURL& Extension::url() {
+ if (!extension_url_.is_valid())
+ extension_url_ = GURL(std::string(chrome::kExtensionScheme) +
+ chrome::kStandardSchemeSeparator + id_ + "/");
+
+ return extension_url_;
+}
+
+void Extension::set_id(const std::string& id) {
+ id_ = id;
+
+ // Reset url_ so that it gets reinitialized next time.
+ extension_url_ = GURL();
+}
+
const std::string Extension::VersionString() const {
return version_->GetString();
}
@@ -320,9 +335,10 @@ bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
return true;
}
-bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
+bool Extension::InitFromValue(const DictionaryValue& source,
std::string* error) {
- // Initialize id.
+ // Initialize id. The ID is not required here because we don't require IDs for
+ // extensions used with --load-extension.
if (source.HasKey(kIdKey)) {
if (!source.GetString(kIdKey, &id_)) {
*error = kInvalidIdError;
@@ -340,23 +356,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
*error = kInvalidIdError;
return false;
}
- } else if (require_id) {
- *error = kInvalidIdError;
- return false;
- } else {
- // Generate a random ID
- static int counter = 0;
- id_ = StringPrintf("%x", counter);
- ++counter;
-
- // pad the string out to 40 chars with zeroes.
- id_.insert(0, 40 - id_.length(), '0');
}
- // Initialize the URL.
- extension_url_ = GURL(std::string(chrome::kExtensionScheme) +
- chrome::kStandardSchemeSeparator + id_ + "/");
-
// Initialize version.
std::string version_str;
if (!source.GetString(kVersionKey, &version_str)) {
@@ -456,7 +457,6 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
UserScript script;
if (!LoadUserScriptHelper(content_script, i, error, &script))
return false; // Failed to parse script context definition
- script.set_extension_id(id());
content_scripts_.push_back(script);
}
}
diff --git a/chrome/browser/extensions/extension.h b/chrome/browser/extensions/extension.h
index 2f59115..5c3d9b8f 100644
--- a/chrome/browser/extensions/extension.h
+++ b/chrome/browser/extensions/extension.h
@@ -100,10 +100,7 @@ class Extension {
}
// Initialize the extension from a parsed manifest.
- // If |require_id| is true, will return an error if the "id" key is missing
- // from the value.
- bool InitFromValue(const DictionaryValue& value, bool require_id,
- std::string* error);
+ bool InitFromValue(const DictionaryValue& value, std::string* error);
// Returns an absolute path to a resource inside of an extension if the
// extension has a theme defined with the given |resource_id|. Otherwise
@@ -113,8 +110,9 @@ class Extension {
FilePath GetThemeResourcePath(const int resource_id);
const FilePath& path() const { return path_; }
- const GURL& url() const { return extension_url_; }
+ const GURL& url();
const std::string& id() const { return id_; }
+ void set_id(const std::string& id);
const Version* version() const { return version_.get(); }
// String representation of the version number.
const std::string VersionString() const;
diff --git a/chrome/browser/extensions/extension_content_script_inject_unittest.cc b/chrome/browser/extensions/extension_content_script_inject_unittest.cc
deleted file mode 100755
index 750176b..0000000
--- a/chrome/browser/extensions/extension_content_script_inject_unittest.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// 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/browser.h"
-#include "chrome/browser/extensions/extension_error_reporter.h"
-#include "chrome/browser/extensions/test_extension_loader.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/test/in_process_browser_test.h"
-#include "chrome/test/ui_test_utils.h"
-#include "net/base/net_util.h"
-
-namespace {
-
-// The extension we're using as our test case.
-const char* kExtensionId = "00123456789abcdef0123456789abcdef0123456";
-
-} // namespace
-
-class ExtensionContentScriptInjectTest : public InProcessBrowserTest {
- public:
- virtual void SetUp() {
- // Initialize the error reporter here, otherwise BrowserMain will create it
- // with the wrong MessageLoop.
- ExtensionErrorReporter::Init(false);
-
- InProcessBrowserTest::SetUp();
- }
-};
-
-// Tests that an extension's user script gets injected into content.
-IN_PROC_BROWSER_TEST_F(ExtensionContentScriptInjectTest, Simple) {
- // Get the path to our extension.
- FilePath extension_path;
- ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extension_path));
- extension_path = extension_path.AppendASCII("extensions").
- AppendASCII("content_script_inject");
- ASSERT_TRUE(file_util::DirectoryExists(extension_path)); // sanity check
-
- // Load it.
- TestExtensionLoader loader(browser()->profile());
- Extension* extension = loader.Load(kExtensionId, extension_path);
- ASSERT_TRUE(extension);
-
- // Get the file URL to our test page.
- FilePath test_page_path;
- ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_page_path));
- test_page_path = test_page_path.AppendASCII("extensions").
- AppendASCII("content_script_inject_page.html");
- ASSERT_TRUE(file_util::PathExists(test_page_path)); // sanity check
- GURL test_page_url = net::FilePathToFileURL(test_page_path);
-
- ui_test_utils::NavigateToURL(browser(), test_page_url);
- TabContents* tabContents = browser()->GetSelectedTabContents();
-
- // The injected user script will set the page title upon execution.
- const char kExpectedTitle[] =
- "testScriptFilesRunInSameContext,testContentInteraction,"
- "testCSSWasInjected,testCannotSeeOtherContentScriptGlobals,"
- "testRunAtDocumentStart,testGotLoadEvents,";
- EXPECT_EQ(ASCIIToUTF16(kExpectedTitle), tabContents->GetTitle());
-}
diff --git a/chrome/browser/extensions/extension_ui_unittest.cc b/chrome/browser/extensions/extension_ui_unittest.cc
index c3dbbb0..0e24832 100644
--- a/chrome/browser/extensions/extension_ui_unittest.cc
+++ b/chrome/browser/extensions/extension_ui_unittest.cc
@@ -39,7 +39,7 @@ namespace {
scoped_ptr<DictionaryValue> extension_data(DeserializeJSONTestData(
manifest_path, &error));
EXPECT_EQ("", error);
- EXPECT_TRUE(extension.InitFromValue(*extension_data, true, &error));
+ EXPECT_TRUE(extension.InitFromValue(*extension_data, &error));
EXPECT_EQ("", error);
scoped_ptr<DictionaryValue>expected_output_data(DeserializeJSONTestData(
diff --git a/chrome/browser/extensions/extension_unittest.cc b/chrome/browser/extensions/extension_unittest.cc
index f160117..5cde5ca 100644
--- a/chrome/browser/extensions/extension_unittest.cc
+++ b/chrome/browser/extensions/extension_unittest.cc
@@ -38,52 +38,47 @@ TEST(ExtensionTest, InitFromValueInvalid) {
static_cast<DictionaryValue*>(serializer.Deserialize(&error)));
ASSERT_TRUE(valid_value.get());
ASSERT_EQ("", error);
- ASSERT_TRUE(extension.InitFromValue(*valid_value, true, &error));
+ ASSERT_TRUE(extension.InitFromValue(*valid_value, &error));
ASSERT_EQ("", error);
scoped_ptr<DictionaryValue> input_value;
// Test missing and invalid ids
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
- input_value->Remove(Extension::kIdKey, NULL);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
- EXPECT_EQ(Extension::kInvalidIdError, error);
-
- input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->SetInteger(Extension::kIdKey, 42);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidIdError, error);
// Test missing and invalid versions
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->Remove(Extension::kVersionKey, NULL);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidVersionError, error);
input_value->SetInteger(Extension::kVersionKey, 42);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidVersionError, error);
// Test missing and invalid names
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->Remove(Extension::kNameKey, NULL);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidNameError, error);
input_value->SetInteger(Extension::kNameKey, 42);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidNameError, error);
// Test invalid description
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->SetInteger(Extension::kDescriptionKey, 42);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidDescriptionError, error);
// Test invalid user scripts list
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->SetInteger(Extension::kContentScriptsKey, 42);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_EQ(Extension::kInvalidContentScriptsListError, error);
// Test invalid user script item
@@ -92,7 +87,7 @@ TEST(ExtensionTest, InitFromValueInvalid) {
input_value->GetList(Extension::kContentScriptsKey, &content_scripts);
ASSERT_FALSE(NULL == content_scripts);
content_scripts->Set(0, Value::CreateIntegerValue(42));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidContentScriptError));
// Test missing and invalid matches array
@@ -101,21 +96,21 @@ TEST(ExtensionTest, InitFromValueInvalid) {
DictionaryValue* user_script = NULL;
content_scripts->GetDictionary(0, &user_script);
user_script->Remove(Extension::kMatchesKey, NULL);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidMatchesError));
user_script->Set(Extension::kMatchesKey, Value::CreateIntegerValue(42));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidMatchesError));
ListValue* matches = new ListValue;
user_script->Set(Extension::kMatchesKey, matches);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidMatchCountError));
// Test invalid match element
matches->Set(0, Value::CreateIntegerValue(42));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidMatchError));
// Test missing and invalid files array
@@ -124,52 +119,52 @@ TEST(ExtensionTest, InitFromValueInvalid) {
content_scripts->GetDictionary(0, &user_script);
user_script->Remove(Extension::kJsKey, NULL);
user_script->Remove(Extension::kCssKey, NULL);
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kMissingFileError));
user_script->Set(Extension::kJsKey, Value::CreateIntegerValue(42));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ 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, true, &error));
+ 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, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kMissingFileError));
// Test invalid file element
files->Set(0, Value::CreateIntegerValue(42));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidJsError));
user_script->Remove(Extension::kJsKey, NULL);
// Test the css element
user_script->Set(Extension::kCssKey, Value::CreateIntegerValue(42));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ 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, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidCssError));
// Test missing and invalid permissions array
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
- EXPECT_TRUE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_TRUE(extension.InitFromValue(*input_value, &error));
ListValue* permissions = NULL;
input_value->GetList(Extension::kPermissionsKey, &permissions);
ASSERT_FALSE(NULL == permissions);
permissions = new ListValue;
input_value->Set(Extension::kPermissionsKey, permissions);
- EXPECT_TRUE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_TRUE(extension.InitFromValue(*input_value, &error));
const std::vector<std::string>* error_vector =
ExtensionErrorReporter::GetInstance()->GetErrors();
const std::string log_error = error_vector->at(error_vector->size() - 1);
@@ -177,24 +172,24 @@ TEST(ExtensionTest, InitFromValueInvalid) {
Extension::kInvalidPermissionCountWarning));
input_value->Set(Extension::kPermissionsKey, Value::CreateIntegerValue(9));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidPermissionsError));
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->GetList(Extension::kPermissionsKey, &permissions);
permissions->Set(0, Value::CreateIntegerValue(24));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidPermissionError));
permissions->Set(0, Value::CreateStringValue("www.google.com"));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidPermissionError));
// Test permissions scheme.
input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy()));
input_value->GetList(Extension::kPermissionsKey, &permissions);
permissions->Set(0, Value::CreateStringValue("file:///C:/foo.txt"));
- EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error));
+ EXPECT_FALSE(extension.InitFromValue(*input_value, &error));
EXPECT_TRUE(MatchPattern(error, Extension::kInvalidPermissionSchemeError));
}
@@ -214,7 +209,7 @@ TEST(ExtensionTest, InitFromValueValid) {
input_value.SetString(Extension::kVersionKey, "1.0.0.0");
input_value.SetString(Extension::kNameKey, "my extension");
- EXPECT_TRUE(extension.InitFromValue(input_value, true, &error));
+ EXPECT_TRUE(extension.InitFromValue(input_value, &error));
EXPECT_EQ("", error);
EXPECT_EQ("00123456789abcdef0123456789abcdef0123456", extension.id());
EXPECT_EQ("1.0.0.0", extension.VersionString());
@@ -236,7 +231,7 @@ TEST(ExtensionTest, GetResourceURLAndPath) {
"00123456789ABCDEF0123456789ABCDEF0123456");
input_value.SetString(Extension::kVersionKey, "1.0.0.0");
input_value.SetString(Extension::kNameKey, "my extension");
- EXPECT_TRUE(extension.InitFromValue(input_value, true, NULL));
+ EXPECT_TRUE(extension.InitFromValue(input_value, NULL));
EXPECT_EQ(extension.url().spec() + "bar/baz.js",
Extension::GetResourceURL(extension.url(), "bar/baz.js").spec());
diff --git a/chrome/browser/extensions/extension_view_unittest.cc b/chrome/browser/extensions/extension_view_unittest.cc
index 69a1fb9..4cdb91c 100755
--- a/chrome/browser/extensions/extension_view_unittest.cc
+++ b/chrome/browser/extensions/extension_view_unittest.cc
@@ -2,14 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/message_loop.h"
#include "base/ref_counted.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/profile.h"
#include "chrome/browser/extensions/extension_error_reporter.h"
#include "chrome/browser/extensions/extension_view.h"
#include "chrome/browser/extensions/extensions_service.h"
-#include "chrome/browser/extensions/test_extension_loader.h"
#include "chrome/common/chrome_paths.h"
+#include "chrome/common/notification_service.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
@@ -19,11 +21,12 @@ namespace {
// up.
const int kAlertTimeoutMs = 20000;
+// How long to wait for the extension to load before giving up.
+const int kLoadTimeoutMs = 10000;
+
// The extension we're using as our test case.
const char* kExtensionId = "00123456789abcdef0123456789abcdef0123456";
-}; // namespace
-
// This class starts up an extension process and waits until it tries to put
// up a javascript alert.
class MockExtensionView : public ExtensionView {
@@ -57,6 +60,44 @@ class MockExtensionView : public ExtensionView {
bool got_message_;
};
+// This class waits for a specific extension to be loaded.
+class ExtensionLoadedObserver : public NotificationObserver {
+ public:
+ explicit ExtensionLoadedObserver() : extension_(NULL) {
+ registrar_.Add(this, NotificationType::EXTENSIONS_LOADED,
+ NotificationService::AllSources());
+ }
+
+ Extension* WaitForExtension() {
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ new MessageLoop::QuitTask, kLoadTimeoutMs);
+ ui_test_utils::RunMessageLoop();
+ return extension_;
+ }
+
+ private:
+ virtual void Observe(NotificationType type, const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::EXTENSIONS_LOADED) {
+ ExtensionList* extensions = Details<ExtensionList>(details).ptr();
+ for (size_t i = 0; i < (*extensions).size(); i++) {
+ if ((*extensions)[i]->id() == kExtensionId) {
+ extension_ = (*extensions)[i];
+ MessageLoopForUI::current()->Quit();
+ break;
+ }
+ }
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ NotificationRegistrar registrar_;
+ Extension* extension_;
+};
+
+} // namespace
+
class ExtensionViewTest : public InProcessBrowserTest {
public:
virtual void SetUp() {
@@ -74,6 +115,10 @@ class ExtensionViewTest : public InProcessBrowserTest {
// Tests that ExtensionView starts an extension process and runs the script
// contained in the extension's "index.html" file.
IN_PROC_BROWSER_TEST_F(ExtensionViewTest, Index) {
+ // Create an observer first to be sure we have the notification registered
+ // before it's sent.
+ ExtensionLoadedObserver observer;
+
// Get the path to our extension.
FilePath path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
@@ -81,13 +126,17 @@ IN_PROC_BROWSER_TEST_F(ExtensionViewTest, Index) {
AppendASCII("good").AppendASCII("extension1").AppendASCII("1");
ASSERT_TRUE(file_util::DirectoryExists(path)); // sanity check
- // Wait for the extension to load and grab a pointer to it.
- TestExtensionLoader loader(browser()->profile());
- Extension* extension = loader.Load(kExtensionId, path);
+ // Load it.
+ Profile* profile = browser()->profile();
+ profile->GetExtensionsService()->Init();
+ profile->GetExtensionsService()->LoadExtension(path);
+
+ // Now wait for it to load, and grab a pointer to it.
+ Extension* extension = observer.WaitForExtension();
ASSERT_TRUE(extension);
GURL url = Extension::GetResourceURL(extension->url(), "toolstrip1.html");
// Start the extension process and wait for it to show a javascript alert.
- MockExtensionView view(extension, url, browser()->profile());
+ MockExtensionView view(extension, url, profile);
EXPECT_TRUE(view.got_message());
}
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 942d23c..5c836d0 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -258,9 +258,18 @@ void ExtensionsServiceBackend::LoadSingleExtension(
LOG(INFO) << "Loading single extension from " <<
WideToASCII(extension_path.BaseName().ToWStringHack());
- Extension* extension = LoadExtension(extension_path,
- false); // don't require ID
+ Extension* extension = LoadExtension(extension_path);
if (extension) {
+ if (extension->id().empty()) {
+ // Generate an ID
+ static int counter = 0;
+ std::string id = StringPrintf("%x", counter);
+ ++counter;
+
+ // pad the string out to 40 chars with zeroes.
+ id.insert(0, 40 - id.length(), '0');
+ extension->set_id(id);
+ }
ExtensionList* extensions = new ExtensionList;
extensions->push_back(extension);
ReportExtensionsLoaded(extensions);
@@ -281,12 +290,11 @@ Extension* ExtensionsServiceBackend::LoadExtensionCurrentVersion(
WideToASCII(extension_path.BaseName().ToWStringHack()) <<
" version: " << version_str;
- return LoadExtension(extension_path.AppendASCII(version_str),
- true); // require id
+ return LoadExtension(extension_path.AppendASCII(version_str));
}
Extension* ExtensionsServiceBackend::LoadExtension(
- const FilePath& extension_path, bool require_id) {
+ const FilePath& extension_path) {
FilePath manifest_path =
extension_path.AppendASCII(Extension::kManifestFilename);
if (!file_util::PathExists(manifest_path)) {
@@ -309,7 +317,7 @@ Extension* ExtensionsServiceBackend::LoadExtension(
scoped_ptr<Extension> extension(new Extension(extension_path));
if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()),
- require_id, &error)) {
+ &error)) {
ReportExtensionLoadError(extension_path, error);
return NULL;
}
@@ -630,9 +638,7 @@ bool ExtensionsServiceBackend::InstallOrUpdateExtension(
DictionaryValue* dict = manifest.get();
Extension extension;
std::string error;
- if (!extension.InitFromValue(*dict,
- true, // require ID
- &error)) {
+ if (!extension.InitFromValue(*dict, &error)) {
ReportExtensionInstallError(source_file,
"Invalid extension manifest.");
return false;
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
index 64d2e55..5c08685 100644
--- a/chrome/browser/extensions/extensions_service.h
+++ b/chrome/browser/extensions/extensions_service.h
@@ -141,7 +141,7 @@ class ExtensionsServiceBackend
private:
// Load a single extension from |extension_path|, the top directory of
// a specific extension where its manifest file lives.
- Extension* LoadExtension(const FilePath& extension_path, bool require_id);
+ Extension* LoadExtension(const FilePath& extension_path);
// Load a single extension from |extension_path|, the top directory of
// a versioned extension where its Current Version file lives.
diff --git a/chrome/browser/extensions/test_extension_loader.cc b/chrome/browser/extensions/test_extension_loader.cc
deleted file mode 100755
index d8bacb7..0000000
--- a/chrome/browser/extensions/test_extension_loader.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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/test_extension_loader.h"
-
-#include "base/file_path.h"
-#include "base/message_loop.h"
-#include "chrome/browser/profile.h"
-#include "chrome/browser/extensions/extensions_service.h"
-#include "chrome/common/notification_service.h"
-#include "chrome/test/ui_test_utils.h"
-
-namespace {
-
-// How long to wait for the extension to load before giving up.
-const int kLoadTimeoutMs = 5000;
-
-} // namespace
-
-TestExtensionLoader::TestExtensionLoader(Profile* profile)
- : profile_(profile),
- extension_(NULL) {
- registrar_.Add(this, NotificationType::EXTENSIONS_LOADED,
- NotificationService::AllSources());
-
- profile_->GetExtensionsService()->Init();
- DCHECK(profile_->GetExtensionsService()->extensions()->empty());
-}
-
-Extension* TestExtensionLoader::Load(const char* extension_id,
- const FilePath& path) {
- loading_extension_id_ = extension_id;
-
- // Load the extension.
- profile_->GetExtensionsService()->LoadExtension(path);
-
- // Wait for the load to complete. Stick a QuitTask into the message loop
- // with the timeout so it will exit if the extension never loads.
- extension_ = NULL;
- MessageLoop::current()->PostDelayedTask(FROM_HERE,
- new MessageLoop::QuitTask, kLoadTimeoutMs);
- ui_test_utils::RunMessageLoop();
-
- return extension_;
-}
-
-void TestExtensionLoader::Observe(NotificationType type,
- const NotificationSource& source,
- const NotificationDetails& details) {
- if (type == NotificationType::EXTENSIONS_LOADED) {
- ExtensionList* extensions = Details<ExtensionList>(details).ptr();
- for (size_t i = 0; i < (*extensions).size(); ++i) {
- if ((*extensions)[i]->id() == loading_extension_id_) {
- extension_ = (*extensions)[i];
- MessageLoopForUI::current()->Quit();
- break;
- }
- }
- } else {
- NOTREACHED();
- }
-}
diff --git a/chrome/browser/extensions/test_extension_loader.h b/chrome/browser/extensions/test_extension_loader.h
deleted file mode 100755
index 4fb74d9..0000000
--- a/chrome/browser/extensions/test_extension_loader.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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_TEST_EXTENSION_LOADER_H_
-#define CHROME_BROWSER_EXTENSIONS_TEST_EXTENSION_LOADER_H_
-
-#include "chrome/browser/extensions/extension.h"
-#include "chrome/common/notification_observer.h"
-#include "chrome/common/notification_registrar.h"
-
-class Extension;
-class FilePath;
-class Profile;
-
-class TestExtensionLoader : public NotificationObserver {
- public:
- explicit TestExtensionLoader(Profile* profile);
-
- Extension* Load(const char* extension_id, const FilePath& path);
-
- virtual void Observe(NotificationType type,
- const NotificationSource& source,
- const NotificationDetails& details);
-
- private:
- Profile* profile_;
- Extension* extension_;
- NotificationRegistrar registrar_;
- std::string loading_extension_id_;
-
- DISALLOW_COPY_AND_ASSIGN(TestExtensionLoader);
-};
-
-#endif // CHROME_BROWSER_EXTENSIONS_TEST_EXTENSION_LOADER_H_
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index d124b60..8a1dfb4 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -2092,11 +2092,9 @@
'browser/download/download_manager_unittest.cc',
'browser/download/download_request_manager_unittest.cc',
'browser/download/save_package_unittest.cc',
- 'browser/extensions/extension_content_script_inject_unittest.cc',
'browser/extensions/extension_ui_unittest.cc',
'browser/extensions/extension_unittest.cc',
'browser/extensions/extensions_service_unittest.cc',
- 'browser/extensions/test_extension_loader.cc',
'browser/extensions/user_script_master_unittest.cc',
'browser/google_url_tracker_unittest.cc',
'browser/gtk/tabs/tab_renderer_gtk_unittest.cc',
@@ -2273,8 +2271,6 @@
'browser/bookmarks/bookmark_folder_tree_model_unittest.cc',
'browser/bookmarks/bookmark_table_model_unittest.cc',
'browser/browser_commands_unittest.cc',
- 'browser/extensions/extension_content_script_inject_unittest.cc',
- 'browser/extensions/test_extension_loader.cc',
'browser/extensions/user_script_master_unittest.cc',
'browser/importer/firefox_importer_unittest.cc',
'browser/importer/importer_unittest.cc',
diff --git a/chrome/common/extensions/user_script.cc b/chrome/common/extensions/user_script.cc
index abe9280..69fe5f4 100644
--- a/chrome/common/extensions/user_script.cc
+++ b/chrome/common/extensions/user_script.cc
@@ -40,9 +40,6 @@ void UserScript::Pickle(::Pickle* pickle) const {
// Write the run location.
pickle->WriteInt(run_location());
- // Write the extension id.
- pickle->WriteString(extension_id());
-
// Write globs.
pickle->WriteSize(globs_.size());
for (std::vector<std::string>::const_iterator glob = globs_.begin();
@@ -79,9 +76,6 @@ void UserScript::Unpickle(const ::Pickle& pickle, void** iter) {
CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST);
run_location_ = static_cast<RunLocation>(run_location);
- // Read the extension ID.
- CHECK(pickle.ReadString(iter, &extension_id_));
-
// Read globs.
size_t num_globs = 0;
CHECK(pickle.ReadSize(iter, &num_globs));
diff --git a/chrome/common/extensions/user_script.h b/chrome/common/extensions/user_script.h
index 070ead3..1a8d6df 100644
--- a/chrome/common/extensions/user_script.h
+++ b/chrome/common/extensions/user_script.h
@@ -111,11 +111,6 @@ class UserScript {
FileList& css_scripts() { return css_scripts_; }
const FileList& css_scripts() const { return css_scripts_; }
- const std::string& extension_id() const { return extension_id_; }
- void set_extension_id(const std::string& id) { extension_id_ = id; }
-
- bool is_standalone() { return extension_id_.empty(); }
-
// Returns true if the script should be applied to the specified URL, false
// otherwise.
bool MatchesUrl(const GURL& url);
@@ -146,10 +141,6 @@ class UserScript {
// List of css scripts defined in content_scripts
FileList css_scripts_;
-
- // 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_;
};
typedef std::vector<UserScript> UserScriptList;
diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js
index 221f3fd..b109a76 100644
--- a/chrome/renderer/resources/extension_process_bindings.js
+++ b/chrome/renderer/resources/extension_process_bindings.js
@@ -9,9 +9,9 @@ var chromium;
// We shouldn't be receiving evil JSON unless the browser is owned, but just
// to be safe, we sanitize it. This regex mania was borrowed from json2,
// from json.org.
- if (!/^[\],:{}\s]*$/.test(
- str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
- replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ if (!/^[\],:{}\s]*$/.test(
+ str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, '')))
throw new Error("Unexpected characters in incoming JSON response.");
diff --git a/chrome/renderer/user_script_slave.cc b/chrome/renderer/user_script_slave.cc
index bd5a988..aedc345 100644
--- a/chrome/renderer/user_script_slave.cc
+++ b/chrome/renderer/user_script_slave.cc
@@ -25,8 +25,6 @@ using WebKit::WebString;
static const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
static const char kUserScriptTail[] = "\n})(window);";
-static const char kInitSelf[] = "chromium.self = new chromium.Extension('%s')";
-
UserScriptSlave::UserScriptSlave()
: shared_memory_(NULL),
script_deleter_(&scripts_),
@@ -112,8 +110,8 @@ bool UserScriptSlave::InjectScripts(WebFrame* frame,
PerfTimer timer;
int num_matched = 0;
+ std::vector<WebScriptSource> sources;
for (size_t i = 0; i < scripts_.size(); ++i) {
- std::vector<WebScriptSource> sources;
UserScript* script = scripts_[i];
if (!script->MatchesUrl(frame->GetURL()))
continue; // This frame doesn't match the script url pattern, skip it.
@@ -129,35 +127,16 @@ bool UserScriptSlave::InjectScripts(WebFrame* frame,
if (script->run_location() == location) {
for (size_t j = 0; j < script->js_scripts().size(); ++j) {
UserScript::File &file = script->js_scripts()[j];
- std::string content = file.GetContent().as_string();
-
- // We add this dumb function wrapper for standalone user script to
- // emulate what Greasemonkey does.
- if (script->is_standalone()) {
- content.insert(0, kUserScriptHead);
- content += kUserScriptTail;
- }
sources.push_back(WebScriptSource(
- WebString::fromUTF8(content.c_str(), content.length()),
- file.url()));
+ WebString::fromUTF8(file.GetContent()), file.url()));
}
}
+ }
- if (!sources.empty()) {
- if (script->is_standalone()) {
- // For standalone scripts, we try to emulate the Greasemonkey API.
- sources.insert(sources.begin(),
- WebScriptSource(WebString::fromUTF8(api_js_.as_string())));
- } else {
- // Setup chromium.self to contain an Extension object with the correct
- // ID.
- sources.insert(sources.begin(),
- WebScriptSource(WebString::fromUTF8(
- StringPrintf(kInitSelf, script->extension_id().c_str()))));
- }
-
- frame->ExecuteScriptInNewContext(&sources.front(), sources.size());
- }
+ if (!sources.empty()) {
+ sources.insert(
+ sources.begin(), WebScriptSource(WebString::fromUTF8(api_js_)));
+ frame->ExecuteScriptInNewContext(&sources.front(), sources.size());
}
// Log debug info.
diff --git a/chrome/test/data/extensions/content_script_inject/js_test.js b/chrome/test/data/extensions/content_script_inject/js_test.js
deleted file mode 100755
index 87a0930..0000000
--- a/chrome/test/data/extensions/content_script_inject/js_test.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.
-
-// A quick and dirty JavaScript test runner.
-
-function assert(truth) {
- if (!truth) {
- throw new Error('Assertion failed');
- }
-}
-
-function runAllTests() {
- // If there was already an error, do nothing. We don't want to muddy
- // up the results.
- if (document.title.indexOf("Error: ") == 0) {
- return;
- }
-
- for (var propName in window) {
- if (typeof window[propName] == "function" &&
- propName.indexOf("test") == 0) {
- try {
- window[propName]();
- document.title += propName + ",";
- } catch (e) {
- // We use document.title to communicate results back to the browser.
- document.title = "Error: " + propName + ': ' + e.message;
- return;
- }
- }
- }
-}
diff --git a/chrome/test/data/extensions/content_script_inject/manifest.json b/chrome/test/data/extensions/content_script_inject/manifest.json
deleted file mode 100755
index 8214fb6..0000000
--- a/chrome/test/data/extensions/content_script_inject/manifest.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "id": "00123456789ABCDEF0123456789ABCDEF0123456",
- "version": "1.0.0.0",
- "name": "User script inject",
- "content_scripts": [
- {
- "matches": ["file://*/content_script_inject_page.html"],
- "css": ["script1.css"],
- "js": ["js_test.js", "script1a.js", "script1b.js"]
- },
- {
- "matches": ["file://*/content_script_inject_page.html"],
- "js": ["js_test.js", "script2.js"]
- },
- {
- "matches": ["file://*/content_script_inject_page.html"],
- "js": ["js_test.js", "script3.js"],
- "run_at": "document_start"
- }
- ]
-}
diff --git a/chrome/test/data/extensions/content_script_inject/script1.css b/chrome/test/data/extensions/content_script_inject/script1.css
deleted file mode 100644
index 521b501b..0000000
--- a/chrome/test/data/extensions/content_script_inject/script1.css
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-Copyright (c) 2006-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.
-*/
-
-/* script1b.js tests that this rule was applied. */
-body {
- background:red;
-}
diff --git a/chrome/test/data/extensions/content_script_inject/script1a.js b/chrome/test/data/extensions/content_script_inject/script1a.js
deleted file mode 100755
index 87f4f55..0000000
--- a/chrome/test/data/extensions/content_script_inject/script1a.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// 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.
-
-// create a global variable. script1b.js tests to make sure it is visible and
-// script2.js tests to make sure it is not.
-var script1_var = 1;
diff --git a/chrome/test/data/extensions/content_script_inject/script1b.js b/chrome/test/data/extensions/content_script_inject/script1b.js
deleted file mode 100755
index 8e7e102..0000000
--- a/chrome/test/data/extensions/content_script_inject/script1b.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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.
-
-// This tests that we are running in the same global context as script1a.js.
-function testScriptFilesRunInSameContext() {
- assert(script1_var === 1);
-}
-
-// This tests that our relationship to content is working correctly.
-// a) We should not see content globals in our global scope.
-// b) We should have a contentWindow object that looks like a DOM Window.
-// c) We should be able to access content globals via contentWindow.
-function testContentInteraction() {
- assert(typeof content_var == "undefined");
- assert(typeof contentWindow != "undefined");
- assert(contentWindow.location.href.match(/content_script_inject_page.html$/));
- assert(contentWindow.content_var == "hello");
-}
-
-// Test that our css in script1.css was injected successfully.
-function testCSSWasInjected() {
- assert(document.defaultView.getComputedStyle(
- document.body, null)["background-color"] == "rgb(255, 0, 0)");
-}
-
-runAllTests();
diff --git a/chrome/test/data/extensions/content_script_inject/script2.js b/chrome/test/data/extensions/content_script_inject/script2.js
deleted file mode 100755
index 9133afa2..0000000
--- a/chrome/test/data/extensions/content_script_inject/script2.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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.
-
-// Tests that we cannot see global variables defined in other content scripts.
-// This var was defined in script1a.js. We run each content script in a separate
-// context, so we shouldn't see globals across them.
-function testCannotSeeOtherContentScriptGlobals() {
- assert(typeof script1_var == "undefined");
-}
-
-runAllTests();
diff --git a/chrome/test/data/extensions/content_script_inject/script3.js b/chrome/test/data/extensions/content_script_inject/script3.js
deleted file mode 100644
index 15bc225..0000000
--- a/chrome/test/data/extensions/content_script_inject/script3.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.
-
-var gotDOMContentLoadedEvent = false;
-
-// Test that at parse time, we have the document element.
-// This basically tests that we don't get injected too early (before there is
-// a document element).
-var hasDocumentElement = (document.documentElement.tagName == "HTML");
-
-// TODO(aa): We would like to add more tests here verifying that we aren't
-// injected too late. For example, we could test that there are zero child
-// nodes to the documentElement, but unfortunately run_at:document_start is
-// currently buggy and doesn't guarantee that.
-
-window.addEventListener("DOMContentLoaded", function() {
- gotDOMContentLoadedEvent = true;
-}, false);
-
-// Don't run tests until onload so that we can test that DOMContentLoaded and
-// onload happen after this script runs.
-window.addEventListener("load", runAllTests, false);
-
-function testRunAtDocumentStart() {
- assert(hasDocumentElement);
-}
-
-function testGotLoadEvents() {
- assert(gotDOMContentLoadedEvent);
-}
diff --git a/chrome/test/data/extensions/content_script_inject_page.html b/chrome/test/data/extensions/content_script_inject_page.html
deleted file mode 100755
index 4412e19..0000000
--- a/chrome/test/data/extensions/content_script_inject_page.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-<head>
-<title></title>
-<script>
-var content_var = "hello";
-</script>
-</head>
-<body>
-</body>
-</html>
diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj
index 0b2c1df..d4e3864 100644
--- a/chrome/test/unit/unittests.vcproj
+++ b/chrome/test/unit/unittests.vcproj
@@ -472,10 +472,6 @@
>
</File>
<File
- RelativePath="..\..\browser\extensions\extension_content_script_inject_unittest.cc"
- >
- </File>
- <File
RelativePath="..\..\browser\extensions\extension_ui_unittest.cc"
>
</File>
@@ -704,14 +700,6 @@
>
</File>
<File
- RelativePath="..\..\browser\extensions\test_extension_loader.cc"
- >
- </File>
- <File
- RelativePath="..\..\browser\extensions\test_extension_loader.h"
- >
- </File>
- <File
RelativePath="..\..\browser\history\text_database_manager_unittest.cc"
>
</File>