summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-11 00:20:09 +0000
committercira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-11 00:20:09 +0000
commit1e07f78cf624f3ab8f14a21d80c8999595da21ea (patch)
tree41002a66bbe994cf56155696d309fbbc159c2d02
parent9393b71775c7ea64004df0461d0564361bddd553 (diff)
downloadchromium_src-1e07f78cf624f3ab8f14a21d80c8999595da21ea.zip
chromium_src-1e07f78cf624f3ab8f14a21d80c8999595da21ea.tar.gz
chromium_src-1e07f78cf624f3ab8f14a21d80c8999595da21ea.tar.bz2
Replace __MSG_some_name__ template within extension css files with localized messages.
We avoid replacing messages within html and js extension files for security reasons. Also, developers can already localize messages in html/js using chrome.i18n.getMessage calls. TEST=Localize extension, try body{direction: __MSG_@@bidi_reversed_dir__;} in popup.css, while using non-rtl locale. Text should be alligned to the right (as if we were using rtl locale). BUG=26144 Review URL: http://codereview.chromium.org/570007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38717 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host.cc3
-rw-r--r--chrome/browser/renderer_host/resource_message_filter.cc17
-rw-r--r--chrome/chrome_common.gypi2
-rwxr-xr-xchrome/chrome_tests.gypi1
-rw-r--r--chrome/common/extensions/extension_l10n_util.cc13
-rw-r--r--chrome/common/extensions/extension_l10n_util.h10
-rw-r--r--chrome/common/extensions/extension_l10n_util_unittest.cc96
-rw-r--r--chrome/common/extensions/extension_message_bundle.cc28
-rw-r--r--chrome/common/extensions/extension_message_bundle.h27
-rw-r--r--chrome/common/extensions/extension_message_bundle_unittest.cc33
-rw-r--r--chrome/common/extensions/extension_message_filter_peer.cc130
-rw-r--r--chrome/common/extensions/extension_message_filter_peer.h80
-rw-r--r--chrome/common/extensions/extension_message_filter_peer_unittest.cc260
-rw-r--r--chrome/common/filter_policy.h5
-rw-r--r--chrome/common/resource_dispatcher.cc42
-rw-r--r--chrome/common/resource_dispatcher.h10
-rw-r--r--chrome/renderer/extensions/renderer_extension_bindings.cc25
17 files changed, 732 insertions, 50 deletions
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.cc b/chrome/browser/renderer_host/resource_dispatcher_host.cc
index 4885423..9a245c7 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host.cc
+++ b/chrome/browser/renderer_host/resource_dispatcher_host.cc
@@ -51,6 +51,7 @@
#include "chrome/browser/ssl/ssl_manager.h"
#include "chrome/browser/worker_host/worker_service.h"
#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/render_messages.h"
#include "net/base/auth.h"
@@ -465,6 +466,8 @@ void ResourceDispatcherHost::BeginRequest(
ResourceType::IsFrame(request_data.resource_type), // allow_download
request_data.host_renderer_id,
request_data.host_render_view_id);
+ extension_l10n_util::ApplyMessageFilterPolicy(
+ request_data.url, request_data.resource_type, extra_info);
SetRequestInfo(request, extra_info); // Request takes ownership.
chrome_browser_net::SetOriginProcessUniqueIDForRequest(
request_data.origin_child_id, request);
diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc
index 7cf9b0e..6f427c3 100644
--- a/chrome/browser/renderer_host/resource_message_filter.cc
+++ b/chrome/browser/renderer_host/resource_message_filter.cc
@@ -1343,14 +1343,17 @@ void ResourceMessageFilter::OnGetExtensionMessageBundleOnFileThread(
IPC::Message* reply_msg) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
- std::string error;
- scoped_ptr<ExtensionMessageBundle> bundle(
- extension_file_util::LoadExtensionMessageBundle(
- extension_path, default_locale, &error));
-
std::map<std::string, std::string> dictionary_map;
- if (bundle.get())
- dictionary_map = *bundle->dictionary();
+ if (!default_locale.empty()) {
+ // Touch disk only if extension is localized.
+ std::string error;
+ scoped_ptr<ExtensionMessageBundle> bundle(
+ extension_file_util::LoadExtensionMessageBundle(
+ extension_path, default_locale, &error));
+
+ if (bundle.get())
+ dictionary_map = *bundle->dictionary();
+ }
ViewHostMsg_GetExtensionMessageBundle::WriteReplyParams(
reply_msg, dictionary_map);
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 9391b6d..59097a4 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -153,6 +153,8 @@
'common/extensions/extension_l10n_util.h',
'common/extensions/extension_message_bundle.cc',
'common/extensions/extension_message_bundle.h',
+ 'common/extensions/extension_message_filter_peer.cc',
+ 'common/extensions/extension_message_filter_peer.h',
'common/extensions/extension_resource.cc',
'common/extensions/extension_resource.h',
'common/extensions/extension_unpacker.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 01c3a9a..950e04b 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -840,6 +840,7 @@
'common/extensions/extension_action_unittest.cc',
'common/extensions/extension_l10n_util_unittest.cc',
'common/extensions/extension_message_bundle_unittest.cc',
+ 'common/extensions/extension_message_filter_peer_unittest.cc',
'common/extensions/extension_unpacker_unittest.cc',
'common/extensions/update_manifest_unittest.cc',
'common/extensions/url_pattern_unittest.cc',
diff --git a/chrome/common/extensions/extension_l10n_util.cc b/chrome/common/extensions/extension_l10n_util.cc
index dcc8cc5..2fd3f92 100644
--- a/chrome/common/extensions/extension_l10n_util.cc
+++ b/chrome/common/extensions/extension_l10n_util.cc
@@ -15,10 +15,12 @@
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_file_util.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.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/json_value_serializer.h"
+#include "chrome/common/url_constants.h"
#include "unicode/uloc.h"
namespace errors = extension_manifest_errors;
@@ -294,4 +296,15 @@ ExtensionMessageBundle* LoadMessageCatalogs(
return ExtensionMessageBundle::Create(catalogs, error);
}
+void ApplyMessageFilterPolicy(const GURL& url,
+ const ResourceType::Type& resource_type,
+ ResourceDispatcherHostRequestInfo* request_info) {
+ // Apply filter only to chrome extension css files that don't have
+ // security filter already set.
+ if (url.SchemeIs(chrome::kExtensionScheme) &&
+ request_info->filter_policy() == FilterPolicy::DONT_FILTER &&
+ resource_type == ResourceType::STYLESHEET)
+ request_info->set_filter_policy(FilterPolicy::FILTER_EXTENSION_MESSAGES);
+}
+
} // namespace extension_l10n_util
diff --git a/chrome/common/extensions/extension_l10n_util.h b/chrome/common/extensions/extension_l10n_util.h
index 60733d1..a63bfdb 100644
--- a/chrome/common/extensions/extension_l10n_util.h
+++ b/chrome/common/extensions/extension_l10n_util.h
@@ -11,10 +11,14 @@
#include <string>
#include <vector>
+#include "webkit/glue/resource_type.h"
+
class DictionaryValue;
class Extension;
class ExtensionMessageBundle;
class FilePath;
+class GURL;
+class ResourceDispatcherHostRequestInfo;
struct ExtensionInfo;
namespace extension_l10n_util {
@@ -91,6 +95,12 @@ ExtensionMessageBundle* LoadMessageCatalogs(
const std::set<std::string>& valid_locales,
std::string* error);
+// Applies FilterPolicy::FILTER_EXTENSION_MESSAGES to all text/css requests
+// that have "chrome-extension://" scheme.
+void ApplyMessageFilterPolicy(const GURL& url,
+ const ResourceType::Type& resource_type,
+ ResourceDispatcherHostRequestInfo* request_info);
+
} // namespace extension_l10n_util
#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_L10N_UTIL_H_
diff --git a/chrome/common/extensions/extension_l10n_util_unittest.cc b/chrome/common/extensions/extension_l10n_util_unittest.cc
index 93b16d3..278a4ed 100644
--- a/chrome/common/extensions/extension_l10n_util_unittest.cc
+++ b/chrome/common/extensions/extension_l10n_util_unittest.cc
@@ -10,12 +10,15 @@
#include "base/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
#include "base/values.h"
+#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h"
+#include "chrome/browser/renderer_host/resource_handler.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/extensions/extension_message_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/resource_type.h"
namespace errors = extension_manifest_errors;
namespace keys = extension_manifest_keys;
@@ -379,4 +382,97 @@ TEST(ExtensionL10nUtil, ShouldRelocalizeManifestDifferentCurrentLocale) {
EXPECT_TRUE(extension_l10n_util::ShouldRelocalizeManifest(info));
}
+class DummyResourceHandler : public ResourceHandler {
+ public:
+ DummyResourceHandler() {}
+
+ bool OnRequestRedirected(int request_id, const GURL& url,
+ ResourceResponse* response, bool* defer) {
+ return true;
+ }
+
+ bool OnResponseStarted(int request_id, ResourceResponse* response) {
+ return true;
+ }
+
+ bool OnWillRead(
+ int request_id, net::IOBuffer** buf, int* buf_size, int min_size) {
+ return true;
+ }
+
+ bool OnReadCompleted(int request_id, int* bytes_read) { return true; }
+
+ bool OnResponseCompleted(
+ int request_id, const URLRequestStatus& status, const std::string& info) {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyResourceHandler);
+};
+
+class ApplyMessageFilterPolicyTest : public testing::Test {
+ protected:
+ void SetUp() {
+ url_.reset(new GURL(
+ "chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html"));
+ resource_type_ = ResourceType::STYLESHEET;
+ request_info_.reset(CreateNewResourceRequestInfo());
+ }
+
+ ResourceDispatcherHostRequestInfo* CreateNewResourceRequestInfo() {
+ return new ResourceDispatcherHostRequestInfo(
+ new DummyResourceHandler(), ChildProcessInfo::RENDER_PROCESS, 0, 0, 0,
+ "not important", "not important",
+ ResourceType::STYLESHEET, 0U, false, false, -1, -1);
+ }
+
+ scoped_ptr<GURL> url_;
+ ResourceType::Type resource_type_;
+ scoped_ptr<ResourceDispatcherHostRequestInfo> request_info_;
+};
+
+TEST_F(ApplyMessageFilterPolicyTest, WrongScheme) {
+ url_.reset(new GURL("html://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html"));
+ extension_l10n_util::ApplyMessageFilterPolicy(
+ *url_, resource_type_, request_info_.get());
+
+ EXPECT_EQ(FilterPolicy::DONT_FILTER, request_info_->filter_policy());
+}
+
+TEST_F(ApplyMessageFilterPolicyTest, GoodScheme) {
+ extension_l10n_util::ApplyMessageFilterPolicy(
+ *url_, resource_type_, request_info_.get());
+
+ EXPECT_EQ(FilterPolicy::FILTER_EXTENSION_MESSAGES,
+ request_info_->filter_policy());
+}
+
+TEST_F(ApplyMessageFilterPolicyTest, GoodSchemeWithSecurityFilter) {
+ request_info_->set_filter_policy(FilterPolicy::FILTER_ALL_EXCEPT_IMAGES);
+ extension_l10n_util::ApplyMessageFilterPolicy(
+ *url_, resource_type_, request_info_.get());
+
+ EXPECT_EQ(FilterPolicy::FILTER_ALL_EXCEPT_IMAGES,
+ request_info_->filter_policy());
+}
+
+TEST_F(ApplyMessageFilterPolicyTest, GoodSchemeWrongResourceType) {
+ resource_type_ = ResourceType::MAIN_FRAME;
+ extension_l10n_util::ApplyMessageFilterPolicy(
+ *url_, resource_type_, request_info_.get());
+
+ EXPECT_EQ(FilterPolicy::DONT_FILTER, request_info_->filter_policy());
+}
+
+TEST_F(ApplyMessageFilterPolicyTest, WrongSchemeResourceAndFilter) {
+ url_.reset(new GURL("html://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html"));
+ resource_type_ = ResourceType::MEDIA;
+ request_info_->set_filter_policy(FilterPolicy::FILTER_ALL);
+ extension_l10n_util::ApplyMessageFilterPolicy(
+ *url_, resource_type_, request_info_.get());
+
+ EXPECT_EQ(FilterPolicy::FILTER_ALL, request_info_->filter_policy());
+}
+
} // namespace
diff --git a/chrome/common/extensions/extension_message_bundle.cc b/chrome/common/extensions/extension_message_bundle.cc
index dd2f4c0..0256914 100644
--- a/chrome/common/extensions/extension_message_bundle.cc
+++ b/chrome/common/extensions/extension_message_bundle.cc
@@ -11,6 +11,7 @@
#include "base/hash_tables.h"
#include "base/linked_ptr.h"
#include "base/scoped_ptr.h"
+#include "base/singleton.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "base/values.h"
@@ -209,7 +210,13 @@ bool ExtensionMessageBundle::ReplacePlaceholders(
bool ExtensionMessageBundle::ReplaceMessages(std::string* text,
std::string* error) const {
- return ReplaceVariables(dictionary_, kMessageBegin, kMessageEnd, text, error);
+ return ReplaceMessagesWithExternalDictionary(dictionary_, text, error);
+}
+
+// static
+bool ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary(
+ const SubstitutionMap& dictionary, std::string* text, std::string* error) {
+ return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error);
}
// static
@@ -283,3 +290,22 @@ std::string ExtensionMessageBundle::GetL10nMessage(
return "";
}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper functions.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() {
+ return &Singleton<ExtensionToMessagesMap>()->messages_map;
+}
+
+L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) {
+ ExtensionToL10nMessagesMap::iterator it =
+ Singleton<ExtensionToMessagesMap>()->messages_map.find(extension_id);
+ if (it != Singleton<ExtensionToMessagesMap>()->messages_map.end())
+ return &(it->second);
+
+ return NULL;
+}
diff --git a/chrome/common/extensions/extension_message_bundle.h b/chrome/common/extensions/extension_message_bundle.h
index eb6eaa4..6e59fe3 100644
--- a/chrome/common/extensions/extension_message_bundle.h
+++ b/chrome/common/extensions/extension_message_bundle.h
@@ -71,6 +71,9 @@ class ExtensionMessageBundle {
// Returns false if there is a message in text that's not defined in the
// dictionary.
bool ReplaceMessages(std::string* text, std::string* error) const;
+ // Static version that accepts dictionary.
+ static bool ReplaceMessagesWithExternalDictionary(
+ const SubstitutionMap& dictionary, std::string* text, std::string* error);
// Replaces each occurance of variable placeholder with its value.
// I.e. replaces __MSG_name__ with value from the catalog with the key "name".
@@ -145,4 +148,28 @@ class ExtensionMessageBundle {
SubstitutionMap dictionary_;
};
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper typedefs and functions.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// A map of message name to message.
+typedef std::map<std::string, std::string> L10nMessagesMap;
+
+// A map of extension ID to l10n message map.
+typedef std::map<std::string, L10nMessagesMap > ExtensionToL10nMessagesMap;
+
+// Unique class for Singleton.
+struct ExtensionToMessagesMap {
+ // Maps extension ID to message map.
+ ExtensionToL10nMessagesMap messages_map;
+};
+
+// Returns the extension_id to messages map.
+ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap();
+
+// Returns message map that matches given extension_id, or NULL.
+L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id);
+
#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_BUNDLE_H_
diff --git a/chrome/common/extensions/extension_message_bundle_unittest.cc b/chrome/common/extensions/extension_message_bundle_unittest.cc
index f4d1f77..94ffd37 100644
--- a/chrome/common/extensions/extension_message_bundle_unittest.cc
+++ b/chrome/common/extensions/extension_message_bundle_unittest.cc
@@ -390,3 +390,36 @@ TEST(ExtensionMessageBundle, ReplaceMessagesInText) {
EXPECT_EQ(test_cases[i].result, text);
}
}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Renderer helper functions test.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) {
+ ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap();
+ ASSERT_TRUE(NULL != map1);
+
+ ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap();
+ ASSERT_EQ(map1, map2);
+}
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) {
+ const std::string extension_id("some_unique_12334212314234_id");
+ L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
+ EXPECT_TRUE(NULL == map);
+}
+
+TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) {
+ const std::string extension_id("some_unique_121212121212121_id");
+ // Store a map for given id.
+ L10nMessagesMap messages;
+ messages.insert(std::make_pair("message_name", "message_value"));
+ (*GetExtensionToL10nMessagesMap())[extension_id] = messages;
+
+ L10nMessagesMap* map = GetL10nMessagesMap(extension_id);
+ ASSERT_TRUE(NULL != map);
+ EXPECT_EQ(1U, map->size());
+ EXPECT_EQ("message_value", (*map)["message_name"]);
+}
diff --git a/chrome/common/extensions/extension_message_filter_peer.cc b/chrome/common/extensions/extension_message_filter_peer.cc
new file mode 100644
index 0000000..1c0c1d0
--- /dev/null
+++ b/chrome/common/extensions/extension_message_filter_peer.cc
@@ -0,0 +1,130 @@
+// 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/common/extensions/extension_message_filter_peer.h"
+
+#include "app/l10n_util.h"
+#include "base/string_util.h"
+#include "chrome/common/extensions/extension_message_bundle.h"
+#include "chrome/common/render_messages.h"
+#include "grit/generated_resources.h"
+#include "grit/renderer_resources.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "webkit/glue/webkit_glue.h"
+
+ExtensionMessageFilterPeer::ExtensionMessageFilterPeer(
+ webkit_glue::ResourceLoaderBridge::Peer* peer,
+ IPC::Message::Sender* message_sender,
+ const GURL& request_url)
+ : original_peer_(peer),
+ message_sender_(message_sender),
+ request_url_(request_url) {
+}
+
+ExtensionMessageFilterPeer::~ExtensionMessageFilterPeer() {
+}
+
+// static
+ExtensionMessageFilterPeer*
+ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer(
+ webkit_glue::ResourceLoaderBridge::Peer* peer,
+ IPC::Message::Sender* message_sender,
+ const std::string& mime_type,
+ FilterPolicy::Type filter_policy,
+ const GURL& request_url) {
+ if (filter_policy != FilterPolicy::FILTER_EXTENSION_MESSAGES)
+ return NULL;
+
+ if (StartsWithASCII(mime_type, "text/css", false))
+ return new ExtensionMessageFilterPeer(peer, message_sender, request_url);
+
+ // Return NULL if content is not text/css or it doesn't belong to extension
+ // scheme.
+ return NULL;
+}
+
+void ExtensionMessageFilterPeer::OnUploadProgress(
+ uint64 position, uint64 size) {
+ NOTREACHED();
+}
+
+bool ExtensionMessageFilterPeer::OnReceivedRedirect(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies) {
+ NOTREACHED();
+ return false;
+}
+
+void ExtensionMessageFilterPeer::OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered) {
+ response_info_ = info;
+}
+
+void ExtensionMessageFilterPeer::OnReceivedData(const char* data, int len) {
+ data_.append(data, len);
+}
+
+void ExtensionMessageFilterPeer::OnCompletedRequest(
+ const URLRequestStatus& status, const std::string& security_info) {
+ // Make sure we delete ourselves at the end of this call.
+ scoped_ptr<ExtensionMessageFilterPeer> this_deleter(this);
+
+ // Give sub-classes a chance at altering the data.
+ if (status.status() != URLRequestStatus::SUCCESS) {
+ // We failed to load the resource.
+ original_peer_->OnReceivedResponse(response_info_, true);
+ URLRequestStatus status(URLRequestStatus::CANCELED, net::ERR_ABORTED);
+ original_peer_->OnCompletedRequest(status, security_info);
+ return;
+ }
+
+ ReplaceMessages();
+
+ original_peer_->OnReceivedResponse(response_info_, true);
+ if (!data_.empty())
+ original_peer_->OnReceivedData(data_.data(),
+ static_cast<int>(data_.size()));
+ original_peer_->OnCompletedRequest(status, security_info);
+}
+
+GURL ExtensionMessageFilterPeer::GetURLForDebugging() const {
+ return original_peer_->GetURLForDebugging();
+}
+
+void ExtensionMessageFilterPeer::ReplaceMessages() {
+ if (!message_sender_ || data_.empty())
+ return;
+
+ if (!request_url_.is_valid())
+ return;
+
+ std::string extension_id = request_url_.host();
+ L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id);
+ if (!l10n_messages) {
+ L10nMessagesMap messages;
+ message_sender_->Send(new ViewHostMsg_GetExtensionMessageBundle(
+ extension_id, &messages));
+
+ // Save messages we got, even if they are empty, so we don't have to
+ // ask again.
+ ExtensionToL10nMessagesMap& l10n_messages_map =
+ *GetExtensionToL10nMessagesMap();
+ l10n_messages_map[extension_id] = messages;
+
+ l10n_messages = GetL10nMessagesMap(extension_id);
+ }
+
+ if (l10n_messages->empty())
+ return;
+
+ std::string error;
+ if (ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary(
+ *l10n_messages, &data_, &error)) {
+ data_.resize(data_.size());
+ }
+}
diff --git a/chrome/common/extensions/extension_message_filter_peer.h b/chrome/common/extensions/extension_message_filter_peer.h
new file mode 100644
index 0000000..137ab36
--- /dev/null
+++ b/chrome/common/extensions/extension_message_filter_peer.h
@@ -0,0 +1,80 @@
+// 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.
+
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_FILTER_PEER_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_FILTER_PEER_H_
+
+#include <string>
+
+#include "chrome/common/filter_policy.h"
+#include "ipc/ipc_message.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+// The ExtensionMessageFilterPeer is a proxy to a
+// webkit_glue::ResourceLoaderBridge::Peer instance. It is used to pre-process
+// extension resources (such as css files).
+// Call the factory method CreateExtensionMessageFilterPeer() to obtain an
+// instance of ExtensionMessageFilterPeer based on the original Peer.
+class ExtensionMessageFilterPeer
+ : public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+ virtual ~ExtensionMessageFilterPeer();
+
+ static ExtensionMessageFilterPeer* CreateExtensionMessageFilterPeer(
+ webkit_glue::ResourceLoaderBridge::Peer* peer,
+ IPC::Message::Sender* message_sender,
+ const std::string& mime_type,
+ FilterPolicy::Type filter_policy,
+ const GURL& request_url);
+
+ // ResourceLoaderBridge::Peer methods.
+ virtual void OnUploadProgress(uint64 position, uint64 size);
+ virtual bool OnReceivedRedirect(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies);
+ virtual void OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered);
+ virtual void OnReceivedData(const char* data, int len);
+ virtual void OnCompletedRequest(const URLRequestStatus& status,
+ const std::string& security_info);
+ virtual GURL GetURLForDebugging() const;
+
+ private:
+ friend class ExtensionMessageFilterPeerTest;
+
+ // Use CreateExtensionMessageFilterPeer to create an instance.
+ ExtensionMessageFilterPeer(
+ webkit_glue::ResourceLoaderBridge::Peer* peer,
+ IPC::Message::Sender* message_sender,
+ const GURL& request_url);
+
+ // Loads message catalogs, and replaces all __MSG_some_name__ templates within
+ // loaded file.
+ void ReplaceMessages();
+
+ // Original peer that handles the request once we are done processing data_.
+ webkit_glue::ResourceLoaderBridge::Peer* original_peer_;
+
+ // We just pass though the response info. This holds the copy of the original.
+ webkit_glue::ResourceLoaderBridge::ResponseInfo response_info_;
+
+ // Sends ViewHostMsg_GetExtensionMessageBundle message to the browser to fetch
+ // message catalog.
+ IPC::Message::Sender* message_sender_;
+
+ // Buffer for incoming data. We wait until OnCompletedRequest before using it.
+ std::string data_;
+
+ // Original request URL.
+ GURL request_url_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionMessageFilterPeer);
+};
+
+#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_FILTER_PEER_H_
diff --git a/chrome/common/extensions/extension_message_filter_peer_unittest.cc b/chrome/common/extensions/extension_message_filter_peer_unittest.cc
new file mode 100644
index 0000000..f0a73d7
--- /dev/null
+++ b/chrome/common/extensions/extension_message_filter_peer_unittest.cc
@@ -0,0 +1,260 @@
+// 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 <map>
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "chrome/common/extensions/extension_message_bundle.h"
+#include "chrome/common/extensions/extension_message_filter_peer.h"
+#include "chrome/common/filter_policy.h"
+#include "ipc/ipc_message.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+using testing::_;
+using testing::StrEq;
+using testing::Return;
+
+static const char* const kExtensionUrl_1 =
+ "chrome-extension://some_id/popup.css";
+
+static const char* const kExtensionUrl_2 =
+ "chrome-extension://some_id2/popup.css";
+
+static const char* const kExtensionUrl_3 =
+ "chrome-extension://some_id3/popup.css";
+
+class MockIpcMessageSender : public IPC::Message::Sender {
+ public:
+ MockIpcMessageSender() {}
+ virtual ~MockIpcMessageSender() {}
+
+ MOCK_METHOD1(Send, bool(IPC::Message* message));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockIpcMessageSender);
+};
+
+class MockResourceLoaderBridgePeer
+ : public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+ MockResourceLoaderBridgePeer() {}
+ virtual ~MockResourceLoaderBridgePeer() {}
+
+ MOCK_METHOD2(OnUploadProgress, void(uint64 position, uint64 size));
+ MOCK_METHOD4(OnReceivedRedirect, bool(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies));
+ MOCK_METHOD2(OnReceivedResponse, void(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered));
+ MOCK_METHOD2(OnReceivedData, void(const char* data, int len));
+ MOCK_METHOD2(OnCompletedRequest, void(
+ const URLRequestStatus& status, const std::string& security_info));
+ MOCK_CONST_METHOD0(GetURLForDebugging, GURL());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockResourceLoaderBridgePeer);
+};
+
+class ExtensionMessageFilterPeerTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ sender_.reset(new MockIpcMessageSender());
+ original_peer_.reset(new MockResourceLoaderBridgePeer());
+ filter_peer_.reset(
+ ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer(
+ original_peer_.get(), sender_.get(), "text/css",
+ FilterPolicy::FILTER_EXTENSION_MESSAGES, GURL(kExtensionUrl_1)));
+ }
+
+ ExtensionMessageFilterPeer* CreateExtensionMessageFilterPeer(
+ const std::string& mime_type,
+ FilterPolicy::Type filter_policy,
+ const GURL& request_url) {
+ return ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer(
+ original_peer_.get(), sender_.get(),
+ mime_type, filter_policy, request_url);
+ }
+
+ std::string GetData(ExtensionMessageFilterPeer* filter_peer) {
+ EXPECT_TRUE(NULL != filter_peer);
+ return filter_peer->data_;
+ }
+
+ void SetData(ExtensionMessageFilterPeer* filter_peer,
+ const std::string& data) {
+ EXPECT_TRUE(NULL != filter_peer);
+ filter_peer->data_ = data;
+ }
+
+ scoped_ptr<MockIpcMessageSender> sender_;
+ scoped_ptr<MockResourceLoaderBridgePeer> original_peer_;
+ scoped_ptr<ExtensionMessageFilterPeer> filter_peer_;
+};
+
+TEST_F(ExtensionMessageFilterPeerTest, CreateWithWrongFilterPolicy) {
+ filter_peer_.reset(CreateExtensionMessageFilterPeer(
+ "text/css", FilterPolicy::DONT_FILTER, GURL(kExtensionUrl_1)));
+ EXPECT_TRUE(NULL == filter_peer_.get());
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, CreateWithWrongMimeType) {
+ filter_peer_.reset(CreateExtensionMessageFilterPeer(
+ "text/html",
+ FilterPolicy::FILTER_EXTENSION_MESSAGES,
+ GURL(kExtensionUrl_1)));
+ EXPECT_TRUE(NULL == filter_peer_.get());
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, CreateWithValidInput) {
+ EXPECT_TRUE(NULL != filter_peer_.get());
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, OnReceivedData) {
+ EXPECT_TRUE(GetData(filter_peer_.get()).empty());
+
+ const std::string data_chunk("12345");
+ filter_peer_->OnReceivedData(data_chunk.c_str(), data_chunk.length());
+
+ EXPECT_EQ(data_chunk, GetData(filter_peer_.get()));
+
+ filter_peer_->OnReceivedData(data_chunk.c_str(), data_chunk.length());
+ EXPECT_EQ(data_chunk + data_chunk, GetData(filter_peer_.get()));
+}
+
+MATCHER_P(IsURLRequestEqual, status, "") { return arg.status() == status; }
+
+TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestBadURLRequestStatus) {
+ // It will self-delete once it exits OnCompletedRequest.
+ ExtensionMessageFilterPeer* filter_peer = filter_peer_.release();
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true));
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(
+ IsURLRequestEqual(URLRequestStatus::CANCELED), ""));
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::FAILED);
+ filter_peer->OnCompletedRequest(status, "");
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestEmptyData) {
+ // It will self-delete once it exits OnCompletedRequest.
+ ExtensionMessageFilterPeer* filter_peer = filter_peer_.release();
+
+ EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0);
+ EXPECT_CALL(*original_peer_, OnReceivedData(_, _)).Times(0);
+ EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true));
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(
+ IsURLRequestEqual(URLRequestStatus::SUCCESS), ""));
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ filter_peer->OnCompletedRequest(status, "");
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestNoCatalogs) {
+ // It will self-delete once it exits OnCompletedRequest.
+ ExtensionMessageFilterPeer* filter_peer = filter_peer_.release();
+
+ SetData(filter_peer, "some text");
+
+ EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0);
+ EXPECT_CALL(*sender_, Send(_));
+
+ std::string data = GetData(filter_peer);
+ EXPECT_CALL(*original_peer_,
+ OnReceivedData(StrEq(data.data()), data.length())).Times(2);
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true)).Times(2);
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(
+ IsURLRequestEqual(URLRequestStatus::SUCCESS), "")).Times(2);
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ filter_peer->OnCompletedRequest(status, "");
+
+ // Test if Send gets called again (it shouldn't be) when first call returned
+ // an empty dictionary.
+ filter_peer = CreateExtensionMessageFilterPeer(
+ "text/css",
+ FilterPolicy::FILTER_EXTENSION_MESSAGES,
+ GURL(kExtensionUrl_1));
+ SetData(filter_peer, "some text");
+ filter_peer->OnCompletedRequest(status, "");
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestWithCatalogs) {
+ // It will self-delete once it exits OnCompletedRequest.
+ ExtensionMessageFilterPeer* filter_peer = CreateExtensionMessageFilterPeer(
+ "text/css",
+ FilterPolicy::FILTER_EXTENSION_MESSAGES,
+ GURL(kExtensionUrl_2));
+
+ L10nMessagesMap messages;
+ messages.insert(std::make_pair("text", "new text"));
+ ExtensionToL10nMessagesMap& l10n_messages_map =
+ *GetExtensionToL10nMessagesMap();
+ l10n_messages_map["some_id2"] = messages;
+
+ SetData(filter_peer, "some __MSG_text__");
+
+ EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0);
+ // We already have messages in memory, Send will be skipped.
+ EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+ // __MSG_text__ gets replaced with "new text".
+ std::string data("some new text");
+ EXPECT_CALL(*original_peer_,
+ OnReceivedData(StrEq(data.data()), data.length()));
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true));
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(
+ IsURLRequestEqual(URLRequestStatus::SUCCESS), ""));
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ filter_peer->OnCompletedRequest(status, "");
+}
+
+TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestReplaceMessagesFails) {
+ // It will self-delete once it exits OnCompletedRequest.
+ ExtensionMessageFilterPeer* filter_peer = CreateExtensionMessageFilterPeer(
+ "text/css",
+ FilterPolicy::FILTER_EXTENSION_MESSAGES,
+ GURL(kExtensionUrl_3));
+
+ L10nMessagesMap messages;
+ messages.insert(std::make_pair("text", "new text"));
+ ExtensionToL10nMessagesMap& l10n_messages_map =
+ *GetExtensionToL10nMessagesMap();
+ l10n_messages_map["some_id3"] = messages;
+
+ std::string message("some __MSG_missing_message__");
+ SetData(filter_peer, message);
+
+ EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0);
+ // We already have messages in memory, Send will be skipped.
+ EXPECT_CALL(*sender_, Send(_)).Times(0);
+
+ // __MSG_missing_message__ is missing, so message stays the same.
+ EXPECT_CALL(*original_peer_,
+ OnReceivedData(StrEq(message.data()), message.length()));
+
+ EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true));
+ EXPECT_CALL(*original_peer_, OnCompletedRequest(
+ IsURLRequestEqual(URLRequestStatus::SUCCESS), ""));
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ filter_peer->OnCompletedRequest(status, "");
+}
diff --git a/chrome/common/filter_policy.h b/chrome/common/filter_policy.h
index efea7c1..51ba1e4 100644
--- a/chrome/common/filter_policy.h
+++ b/chrome/common/filter_policy.h
@@ -11,12 +11,17 @@
// can decide to filter it. The filtering is done in the renderer. This class
// enumerates the different policy that can be used for the filtering. It is
// passed along with resource response messages.
+// It can be used for content post-processing, like message replacement within
+// extension css files.
class FilterPolicy {
public:
enum Type {
// Pass all types of resources through unmodified.
DONT_FILTER = 0,
+ // Post-process extension css files.
+ FILTER_EXTENSION_MESSAGES,
+
// Block all types of resources, except images. For images, modify them to
// indicate that they have been filtered.
// TODO(abarth): This is a misleading name for this enum value. We should
diff --git a/chrome/common/resource_dispatcher.cc b/chrome/common/resource_dispatcher.cc
index 8eb74a5..7cbb2a8 100644
--- a/chrome/common/resource_dispatcher.cc
+++ b/chrome/common/resource_dispatcher.cc
@@ -12,6 +12,7 @@
#include "base/message_loop.h"
#include "base/shared_memory.h"
#include "base/string_util.h"
+#include "chrome/common/extensions/extension_message_filter_peer.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/security_filter_peer.h"
#include "net/base/net_errors.h"
@@ -179,7 +180,8 @@ bool IPCResourceLoaderBridge::Start(Peer* peer) {
peer_ = peer;
// generate the request ID, and append it to the message
- request_id_ = dispatcher_->AddPendingRequest(peer_, request_.resource_type);
+ request_id_ = dispatcher_->AddPendingRequest(
+ peer_, request_.resource_type, request_.url);
return dispatcher_->message_sender()->Send(
new ViewHostMsg_RequestResource(routing_id_, request_id_, request_));
@@ -330,18 +332,28 @@ void ResourceDispatcher::OnReceivedResponse(
PendingRequestInfo& request_info = it->second;
request_info.filter_policy = response_head.filter_policy;
webkit_glue::ResourceLoaderBridge::Peer* peer = request_info.peer;
- if (request_info.filter_policy != FilterPolicy::DONT_FILTER) {
+ webkit_glue::ResourceLoaderBridge::Peer* new_peer = NULL;
+ if (request_info.filter_policy == FilterPolicy::FILTER_EXTENSION_MESSAGES) {
+ new_peer = ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer(
+ peer,
+ message_sender(),
+ response_head.mime_type,
+ request_info.filter_policy,
+ request_info.url);
+ } else if (request_info.filter_policy != FilterPolicy::DONT_FILTER) {
// TODO(jcampan): really pass the loader bridge.
- webkit_glue::ResourceLoaderBridge::Peer* new_peer =
- SecurityFilterPeer::CreateSecurityFilterPeer(
- NULL, peer,
- request_info.resource_type, response_head.mime_type,
- request_info.filter_policy,
- net::ERR_INSECURE_RESPONSE);
- if (new_peer) {
- request_info.peer = new_peer;
- peer = new_peer;
- }
+ new_peer = SecurityFilterPeer::CreateSecurityFilterPeer(
+ NULL,
+ peer,
+ request_info.resource_type,
+ response_head.mime_type,
+ request_info.filter_policy,
+ net::ERR_INSECURE_RESPONSE);
+ }
+
+ if (new_peer) {
+ request_info.peer = new_peer;
+ peer = new_peer;
}
RESOURCE_LOG("Dispatching response for " <<
@@ -452,10 +464,12 @@ void ResourceDispatcher::OnRequestComplete(int request_id,
int ResourceDispatcher::AddPendingRequest(
webkit_glue::ResourceLoaderBridge::Peer* callback,
- ResourceType::Type resource_type) {
+ ResourceType::Type resource_type,
+ const GURL& request_url) {
// Compute a unique request_id for this renderer process.
int id = MakeRequestID();
- pending_requests_[id] = PendingRequestInfo(callback, resource_type);
+ pending_requests_[id] =
+ PendingRequestInfo(callback, resource_type, request_url);
return id;
}
diff --git a/chrome/common/resource_dispatcher.h b/chrome/common/resource_dispatcher.h
index 1d3f25a..92234f3 100644
--- a/chrome/common/resource_dispatcher.h
+++ b/chrome/common/resource_dispatcher.h
@@ -42,7 +42,8 @@ class ResourceDispatcher {
// Adds a request from the pending_requests_ list, returning the new
// requests' ID
int AddPendingRequest(webkit_glue::ResourceLoaderBridge::Peer* callback,
- ResourceType::Type resource_type);
+ ResourceType::Type resource_type,
+ const GURL& request_url);
// Removes a request from the pending_requests_ list, returning true if the
// request was found and removed.
@@ -65,12 +66,14 @@ class ResourceDispatcher {
struct PendingRequestInfo {
PendingRequestInfo() { }
PendingRequestInfo(webkit_glue::ResourceLoaderBridge::Peer* peer,
- ResourceType::Type resource_type)
+ ResourceType::Type resource_type,
+ const GURL& request_url)
: peer(peer),
resource_type(resource_type),
filter_policy(FilterPolicy::DONT_FILTER),
is_deferred(false),
- is_cancelled(false) {
+ is_cancelled(false),
+ url(request_url) {
}
~PendingRequestInfo() { }
webkit_glue::ResourceLoaderBridge::Peer* peer;
@@ -79,6 +82,7 @@ class ResourceDispatcher {
MessageQueue deferred_message_queue;
bool is_deferred;
bool is_cancelled;
+ GURL url;
};
typedef base::hash_map<int, PendingRequestInfo> PendingRequestList;
diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc
index 54231b6..2f50143 100644
--- a/chrome/renderer/extensions/renderer_extension_bindings.cc
+++ b/chrome/renderer/extensions/renderer_extension_bindings.cc
@@ -35,12 +35,6 @@ using bindings_utils::ExtensionBase;
namespace {
-// A map of message name to message.
-typedef std::map<std::string, std::string> L10nMessagesMap;
-
-// A map of extension ID to l10n message map.
-typedef std::map<std::string, L10nMessagesMap > ExtensionToL10nMessagesMap;
-
struct ExtensionData {
struct PortData {
int ref_count; // how many contexts have a handle to this port
@@ -48,8 +42,6 @@ struct ExtensionData {
PortData() : ref_count(0), disconnected(false) {}
};
std::map<int, PortData> ports; // port ID -> data
- // Maps extension ID to message map.
- ExtensionToL10nMessagesMap extension_l10n_messages_map_;
};
static bool HasPortData(int port_id) {
@@ -65,21 +57,6 @@ static void ClearPortData(int port_id) {
Singleton<ExtensionData>::get()->ports.erase(port_id);
}
-static ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() {
- return &Singleton<ExtensionData>()->extension_l10n_messages_map_;
-}
-
-static L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) {
- ExtensionToL10nMessagesMap::iterator it =
- Singleton<ExtensionData>()->extension_l10n_messages_map_.find(
- extension_id);
- if (it != Singleton<ExtensionData>()->extension_l10n_messages_map_.end()) {
- return &(it->second);
- } else {
- return NULL;
- }
-}
-
const char kPortClosedError[] = "Attempting to use a disconnected port object";
const char* kExtensionDeps[] = { EventBindings::kName };
@@ -230,8 +207,6 @@ class ExtensionImpl : public ExtensionBase {
l10n_messages_map[extension_id] = messages;
l10n_messages = GetL10nMessagesMap(extension_id);
- if (!l10n_messages)
- return v8::Undefined();
}
std::string message_name = *v8::String::AsciiValue(args[0]);