summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-06 01:06:49 +0000
committercira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-06 01:06:49 +0000
commitd4da3c5996842171e70a909b63129f7f4c5cf819 (patch)
treea8a80cf2a96e29705dcecb6ec96319a18d2e8358
parentf68a27df5495fc0850ced470cc8954b4c493ee26 (diff)
downloadchromium_src-d4da3c5996842171e70a909b63129f7f4c5cf819.zip
chromium_src-d4da3c5996842171e70a909b63129f7f4c5cf819.tar.gz
chromium_src-d4da3c5996842171e70a909b63129f7f4c5cf819.tar.bz2
Localize CSS files in content scripts (but don't localize JS files).
Add UserScriptSlave unittest. BUG=39899 TEST=List css file in content_scripts section of the manifest. Refer to an image using url(chrome-extension://__MSG_@@extension_id_/image.png); within that css. @@extension_id message should be replaced with actual id of the extension. Review URL: http://codereview.chromium.org/1585013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43684 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/extensions/user_script.h8
-rw-r--r--chrome/renderer/mock_render_thread.cc9
-rw-r--r--chrome/renderer/mock_render_thread.h6
-rw-r--r--chrome/renderer/render_thread.cc4
-rw-r--r--chrome/renderer/user_script_slave.cc74
-rw-r--r--chrome/renderer/user_script_slave.h12
-rw-r--r--chrome/renderer/user_script_slave_unittest.cc177
8 files changed, 266 insertions, 25 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index af6a741..514d240 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -948,6 +948,7 @@
'common/zip_unittest.cc',
'renderer/audio_message_filter_unittest.cc',
'renderer/form_manager_unittest.cc',
+ 'renderer/user_script_slave_unittest.cc',
'renderer/media/audio_renderer_impl_unittest.cc',
'renderer/extensions/extension_api_json_validity_unittest.cc',
'renderer/extensions/extension_api_client_unittest.cc',
diff --git a/chrome/common/extensions/user_script.h b/chrome/common/extensions/user_script.h
index ed84009..de5ff76 100644
--- a/chrome/common/extensions/user_script.h
+++ b/chrome/common/extensions/user_script.h
@@ -1,4 +1,4 @@
-// Copyright 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -62,13 +62,13 @@ class UserScript {
// If external_content_ is set returns it as content otherwise it returns
// content_
const base::StringPiece GetContent() const {
- if (external_content_.data())
+ if (!external_content_.empty())
return external_content_;
else
return content_;
}
void set_external_content(const base::StringPiece& content) {
- external_content_ = content;
+ external_content_.assign(content.begin(), content.end());
}
void set_content(const base::StringPiece& content) {
content_.assign(content.begin(), content.end());
@@ -90,7 +90,7 @@ class UserScript {
// The script content. It can be set to either loaded_content_ or
// externally allocated string.
- base::StringPiece external_content_;
+ std::string external_content_;
// Set when the content is loaded by LoadContent
std::string content_;
diff --git a/chrome/renderer/mock_render_thread.cc b/chrome/renderer/mock_render_thread.cc
index cda4fef..ec2151d 100644
--- a/chrome/renderer/mock_render_thread.cc
+++ b/chrome/renderer/mock_render_thread.cc
@@ -85,6 +85,8 @@ void MockRenderThread::OnMessageReceived(const IPC::Message& msg) {
IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnMsgCreateWidget);
IPC_MESSAGE_HANDLER(ViewHostMsg_OpenChannelToExtension,
OnMsgOpenChannelToExtension);
+ IPC_MESSAGE_HANDLER(ViewHostMsg_GetExtensionMessageBundle,
+ OnMsgGetExtensionMessageBundle);
#if defined(OS_WIN) || defined(OS_MACOSX)
IPC_MESSAGE_HANDLER(ViewHostMsg_GetDefaultPrintSettings,
OnGetDefaultPrintSettings);
@@ -120,6 +122,13 @@ void MockRenderThread::OnMsgOpenChannelToExtension(
*port_id = 0;
}
+void MockRenderThread::OnMsgGetExtensionMessageBundle(
+ const std::string extension_id,
+ std::map<std::string, std::string>* messages) {
+ DCHECK(messages);
+ messages->insert(std::make_pair("@@extension_id", extension_id));
+}
+
#if defined(OS_WIN)
void MockRenderThread::OnDuplicateSection(
base::SharedMemoryHandle renderer_handle,
diff --git a/chrome/renderer/mock_render_thread.h b/chrome/renderer/mock_render_thread.h
index 03fe08f..4722e04 100644
--- a/chrome/renderer/mock_render_thread.h
+++ b/chrome/renderer/mock_render_thread.h
@@ -5,6 +5,7 @@
#ifndef CHROME_RENDERER_MOCK_RENDER_THREAD_H_
#define CHROME_RENDERER_MOCK_RENDER_THREAD_H_
+#include <map>
#include <string>
#include <vector>
@@ -87,6 +88,11 @@ class MockRenderThread : public RenderThreadBase {
const std::string& source_extension_id,
const std::string& target_extension_id, int* port_id);
+ // The callee expect to be returned the map with at least extension_id value.
+ void OnMsgGetExtensionMessageBundle(
+ const std::string extension_id,
+ std::map<std::string, std::string>* messages);
+
#if defined(OS_WIN)
void OnDuplicateSection(base::SharedMemoryHandle renderer_handle,
base::SharedMemoryHandle* browser_handle);
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index a1e3a28..1d0be3e 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -227,7 +227,7 @@ void RenderThread::Init() {
task_factory_.reset(new ScopedRunnableMethodFactory<RenderThread>(this));
visited_link_slave_.reset(new VisitedLinkSlave());
- user_script_slave_.reset(new UserScriptSlave());
+ user_script_slave_.reset(new UserScriptSlave(this));
dns_master_.reset(new RenderDnsMaster());
histogram_snapshots_.reset(new RendererHistogramSnapshots());
appcache_dispatcher_.reset(new AppCacheDispatcher(this));
@@ -463,7 +463,7 @@ void RenderThread::OnSetZoomLevelForCurrentHost(const std::string& host,
void RenderThread::OnUpdateUserScripts(base::SharedMemoryHandle scripts) {
DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle";
- user_script_slave_->UpdateScripts(scripts);
+ user_script_slave_->UpdateScripts(scripts, is_incognito_process());
UpdateActiveExtensions();
}
diff --git a/chrome/renderer/user_script_slave.cc b/chrome/renderer/user_script_slave.cc
index 66c15d1..5f1c780 100644
--- a/chrome/renderer/user_script_slave.cc
+++ b/chrome/renderer/user_script_slave.cc
@@ -15,6 +15,8 @@
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_message_bundle.h"
+#include "chrome/common/render_messages.h"
#include "chrome/renderer/extension_groups.h"
#include "chrome/renderer/render_thread.h"
#include "googleurl/src/gurl.h"
@@ -55,9 +57,10 @@ int UserScriptSlave::GetIsolatedWorldId(const std::string& extension_id) {
return new_id;
}
-UserScriptSlave::UserScriptSlave()
+UserScriptSlave::UserScriptSlave(RenderThreadBase* message_sender)
: shared_memory_(NULL),
- script_deleter_(&scripts_) {
+ script_deleter_(&scripts_),
+ render_thread_(message_sender) {
api_js_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_GREASEMONKEY_API_JS);
}
@@ -69,10 +72,49 @@ void UserScriptSlave::GetActiveExtensions(std::set<std::string>* extension_ids)
}
}
-bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) {
- scripts_.clear();
+// Fetches l10n messages from browser, and populates the map.
+// Returns empty map if extension_id is empty or content script doesn't have
+// css scripts.
+static void GetL10nMessagesForExtension(
+ const UserScript* script,
+ RenderThreadBase* message_sender,
+ L10nMessagesMap* messages) {
+ DCHECK(script);
+ DCHECK(message_sender);
+ DCHECK(messages);
+ const std::string extension_id = script->extension_id();
+ if (!extension_id.empty() && !script->css_scripts().empty()) {
+ // Always fetch messages, since files were updated. We don't want to have
+ // stale content.
+ message_sender->Send(new ViewHostMsg_GetExtensionMessageBundle(
+ extension_id, messages));
+ }
+}
- bool only_inject_incognito = RenderThread::current()->is_incognito_process();
+// Sets external content for each js/css script file.
+// Message placeholders are replaced in scripts if localization catalog is
+// available and should_localize is true.
+static void SetExternalContent(const Pickle& pickle,
+ void** iter,
+ const L10nMessagesMap& l10n_messages,
+ UserScript::File* script,
+ bool should_localize) {
+ const char* body = NULL;
+ int body_length = 0;
+ CHECK(pickle.ReadData(iter, &body, &body_length));
+ std::string localized_body(body, body_length);
+ if (!l10n_messages.empty() && should_localize) {
+ std::string error;
+ ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary(
+ l10n_messages, &localized_body, &error);
+ }
+ script->set_external_content(
+ base::StringPiece(localized_body.data(), localized_body.length()));
+}
+
+bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory,
+ bool is_incognito_process) {
+ scripts_.clear();
// Create the shared memory object (read only).
shared_memory_.reset(new base::SharedMemory(shared_memory, true));
@@ -97,32 +139,30 @@ bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) {
Pickle pickle(reinterpret_cast<char*>(shared_memory_->memory()),
pickle_size);
pickle.ReadSize(&iter, &num_scripts);
-
scripts_.reserve(num_scripts);
for (size_t i = 0; i < num_scripts; ++i) {
scripts_.push_back(new UserScript());
UserScript* script = scripts_.back();
script->Unpickle(pickle, &iter);
+ // We need to fetch message catalogs for each script that has extension id
+ // and at least one css script.
+ L10nMessagesMap l10n_messages;
+ GetL10nMessagesForExtension(script, render_thread_, &l10n_messages);
+
// 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.
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(
- base::StringPiece(body, body_length));
+ SetExternalContent(
+ pickle, &iter, l10n_messages, &script->js_scripts()[j], false);
}
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(
- base::StringPiece(body, body_length));
+ SetExternalContent(
+ pickle, &iter, l10n_messages, &script->css_scripts()[j], true);
}
- if (only_inject_incognito && !script->is_incognito_enabled()) {
+ if (is_incognito_process && !script->is_incognito_enabled()) {
// This script shouldn't run in an incognito tab.
delete script;
scripts_.pop_back();
diff --git a/chrome/renderer/user_script_slave.h b/chrome/renderer/user_script_slave.h
index cf8fb8d..0931a58 100644
--- a/chrome/renderer/user_script_slave.h
+++ b/chrome/renderer/user_script_slave.h
@@ -17,6 +17,8 @@
#include "chrome/common/extensions/user_script.h"
#include "third_party/WebKit/WebKit/chromium/public/WebScriptSource.h"
+class RenderThreadBase;
+
namespace WebKit {
class WebFrame;
}
@@ -26,13 +28,14 @@ using WebKit::WebScriptSource;
// Manages installed UserScripts for a render process.
class UserScriptSlave {
public:
- UserScriptSlave();
+ explicit UserScriptSlave(RenderThreadBase* render_thread);
// Returns the unique set of extension IDs this UserScriptSlave knows about.
void GetActiveExtensions(std::set<std::string>* extension_ids);
// Update the parsed scripts from shared memory.
- bool UpdateScripts(base::SharedMemoryHandle shared_memory);
+ bool UpdateScripts(base::SharedMemoryHandle shared_memory,
+ bool is_incognito_process);
// Inject the appropriate scripts into a frame based on its URL.
// TODO(aa): Extract a UserScriptFrame interface out of this to improve
@@ -44,6 +47,8 @@ class UserScriptSlave {
static void InsertInitExtensionCode(std::vector<WebScriptSource>* sources,
const std::string& extension_id);
private:
+ friend class UserScriptSlaveTest;
+
// Shared memory containing raw script data.
scoped_ptr<base::SharedMemory> shared_memory_;
@@ -51,6 +56,9 @@ class UserScriptSlave {
std::vector<UserScript*> scripts_;
STLElementDeleter<std::vector<UserScript*> > script_deleter_;
+ // RPC message sender for fetching message catalogs.
+ RenderThreadBase* render_thread_;
+
// Greasemonkey API source that is injected with the scripts.
base::StringPiece api_js_;
diff --git a/chrome/renderer/user_script_slave_unittest.cc b/chrome/renderer/user_script_slave_unittest.cc
new file mode 100644
index 0000000..88f2a6b
--- /dev/null
+++ b/chrome/renderer/user_script_slave_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright (c) 2010 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/renderer/user_script_slave.h"
+
+#include "base/pickle.h"
+#include "base/scoped_ptr.h"
+#include "base/shared_memory.h"
+#include "base/string_piece.h"
+#include "chrome/common/extensions/user_script.h"
+#include "chrome/common/ipc_test_sink.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/renderer/mock_render_thread.h"
+#include "chrome/renderer/render_thread.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_sync_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef std::vector<UserScript::File> FileList;
+typedef std::vector<UserScript*> UserScripts;
+
+// Valid extension id.
+const char* const kExtensionId = "nanifionbniojdbjhimhbiajcfajnjde";
+// JS script.
+const char* const kJsScript =
+ "var x = { 'id': '__MSG_@@extension_id__'};";
+// CSS style.
+const char* const kCssScript =
+ "div {url(chrome-extension://__MSG_@@extension_id__/icon.png);}";
+const char* const kCssScriptWithReplacedMessage =
+ "div {url(chrome-extension://nanifionbniojdbjhimhbiajcfajnjde/icon.png);}";
+
+class UserScriptSlaveTest : public testing::Test {
+ public:
+ UserScriptSlaveTest()
+ : shared_memory_(NULL) {
+ }
+
+ virtual void SetUp() {
+ message_sender_.reset(new MockRenderThread());
+ user_script_slave_.reset(new UserScriptSlave(message_sender_.get()));
+ }
+
+ virtual void TearDown() {
+ shared_memory_.release();
+ }
+
+ UserScripts GetUserScripts() {
+ return user_script_slave_->scripts_;
+ }
+
+ protected:
+ // Create user script with given extension id.
+ void CreateUserScript(const std::string extension_id) {
+ // Extension id can be empty.
+ user_script_.set_extension_id(extension_id);
+
+ // Add js script.
+ FileList& js_scripts = user_script_.js_scripts();
+ UserScript::File js_script;
+ js_script.set_content(base::StringPiece(kJsScript, strlen(kJsScript)));
+ js_scripts.push_back(js_script);
+
+ // Add css script.
+ FileList& css_scripts = user_script_.css_scripts();
+ UserScript::File css_script;
+ css_script.set_content(base::StringPiece(kCssScript, strlen(kCssScript)));
+ css_scripts.push_back(css_script);
+ }
+
+ // Serializes the UserScript object and stores it in the shared memory.
+ bool Serialize(const UserScript& script) {
+ Pickle pickle;
+ pickle.WriteSize(1);
+ script.Pickle(&pickle);
+ for (size_t j = 0; j < script.js_scripts().size(); j++) {
+ base::StringPiece contents = script.js_scripts()[j].GetContent();
+ pickle.WriteData(contents.data(), contents.length());
+ }
+ for (size_t j = 0; j < script.css_scripts().size(); j++) {
+ base::StringPiece contents = script.css_scripts()[j].GetContent();
+ pickle.WriteData(contents.data(), contents.length());
+ }
+
+ // Create the shared memory object.
+ shared_memory_.reset(new base::SharedMemory());
+
+ if (!shared_memory_->Create(std::wstring(), // anonymous
+ false, // read-only
+ false, // open existing
+ pickle.size()))
+ return false;
+
+ // Map into our process.
+ if (!shared_memory_->Map(pickle.size()))
+ return false;
+
+ // Copy the pickle to shared memory.
+ memcpy(shared_memory_->memory(), pickle.data(), pickle.size());
+ return true;
+ }
+
+ // User script slave we are testing.
+ scoped_ptr<UserScriptSlave> user_script_slave_;
+
+ // IPC message sender.
+ scoped_ptr<MockRenderThread> message_sender_;
+
+ // User script that has css and js files.
+ UserScript user_script_;
+
+ // Shared memory object used to pass user scripts from browser to renderer.
+ scoped_ptr<base::SharedMemory> shared_memory_;
+};
+
+TEST_F(UserScriptSlaveTest, MessagesNotReplacedIfScriptDoesntHaveCssScript) {
+ CreateUserScript(kExtensionId);
+ user_script_.css_scripts().clear();
+
+ ASSERT_TRUE(Serialize(user_script_));
+ ASSERT_TRUE(user_script_slave_->UpdateScripts(
+ shared_memory_->handle(), false));
+
+ UserScripts scripts = GetUserScripts();
+ ASSERT_EQ(1U, scripts.size());
+ ASSERT_EQ(1U, scripts[0]->js_scripts().size());
+ EXPECT_EQ(0U, scripts[0]->css_scripts().size());
+
+ EXPECT_EQ(scripts[0]->js_scripts()[0].GetContent(), kJsScript);
+
+ // Send was not called to fetch the messages in this case, since we don't
+ // have the css scripts.
+ EXPECT_EQ(0U, message_sender_->sink().message_count());
+}
+
+TEST_F(UserScriptSlaveTest, MessagesNotReplacedIfScriptDoesntHaveExtensionId) {
+ CreateUserScript("");
+ ASSERT_TRUE(Serialize(user_script_));
+ ASSERT_TRUE(user_script_slave_->UpdateScripts(
+ shared_memory_->handle(), false));
+
+ UserScripts scripts = GetUserScripts();
+ ASSERT_EQ(1U, scripts.size());
+ ASSERT_EQ(1U, scripts[0]->js_scripts().size());
+ ASSERT_EQ(1U, scripts[0]->css_scripts().size());
+
+ EXPECT_EQ(scripts[0]->js_scripts()[0].GetContent(), kJsScript);
+ EXPECT_EQ(scripts[0]->css_scripts()[0].GetContent(), kCssScript);
+
+ // Send was not called to fetch the messages in this case, since we don't
+ // have the extension id.
+ EXPECT_EQ(0U, message_sender_->sink().message_count());
+}
+
+TEST_F(UserScriptSlaveTest, MessagesAreReplacedIfScriptHasExtensionId) {
+ CreateUserScript(kExtensionId);
+ ASSERT_TRUE(Serialize(user_script_));
+ ASSERT_TRUE(user_script_slave_->UpdateScripts(
+ shared_memory_->handle(), false));
+
+ UserScripts scripts = GetUserScripts();
+ ASSERT_EQ(1U, scripts.size());
+ ASSERT_EQ(1U, scripts[0]->js_scripts().size());
+ ASSERT_EQ(1U, scripts[0]->css_scripts().size());
+
+ // JS scripts should stay the same.
+ EXPECT_STREQ(scripts[0]->js_scripts()[0].GetContent().data(), kJsScript);
+ // We expect only CSS scripts to change.
+ EXPECT_STREQ(scripts[0]->css_scripts()[0].GetContent().data(),
+ kCssScriptWithReplacedMessage);
+
+ // Send gets always gets called if there is an extension id and css script.
+ ASSERT_EQ(1U, message_sender_->sink().message_count());
+ EXPECT_TRUE(message_sender_->sink().GetFirstMessageMatching(
+ ViewHostMsg_GetExtensionMessageBundle::ID));
+}