diff options
author | cira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-06 01:06:49 +0000 |
---|---|---|
committer | cira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-06 01:06:49 +0000 |
commit | d4da3c5996842171e70a909b63129f7f4c5cf819 (patch) | |
tree | a8a80cf2a96e29705dcecb6ec96319a18d2e8358 | |
parent | f68a27df5495fc0850ced470cc8954b4c493ee26 (diff) | |
download | chromium_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.gypi | 1 | ||||
-rw-r--r-- | chrome/common/extensions/user_script.h | 8 | ||||
-rw-r--r-- | chrome/renderer/mock_render_thread.cc | 9 | ||||
-rw-r--r-- | chrome/renderer/mock_render_thread.h | 6 | ||||
-rw-r--r-- | chrome/renderer/render_thread.cc | 4 | ||||
-rw-r--r-- | chrome/renderer/user_script_slave.cc | 74 | ||||
-rw-r--r-- | chrome/renderer/user_script_slave.h | 12 | ||||
-rw-r--r-- | chrome/renderer/user_script_slave_unittest.cc | 177 |
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)); +} |