diff options
12 files changed, 280 insertions, 136 deletions
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index e40ce74..29622b9 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -1614,6 +1614,7 @@ }, { "namespace": "i18n", + "unprivileged": true, "types": [], "functions": [ { @@ -1633,7 +1634,7 @@ { "name": "getMessage", "type": "function", - "nodoc": true, + "unprivileged": true, "description": "Get a message from the extension language catalog, for a current locale.", "parameters": [ { "type": "string", diff --git a/chrome/common/extensions/docs/content_scripts.html b/chrome/common/extensions/docs/content_scripts.html index 04399ff..1eef989 100644 --- a/chrome/common/extensions/docs/content_scripts.html +++ b/chrome/common/extensions/docs/content_scripts.html @@ -772,5 +772,5 @@ sending a request to its parent extension. </div> <!-- /gc-container --> </body></html> -Blocked access to external URL http://www.youtube.com/v/laLudeUmXHM&hl=en_US&fs=1& Blocked access to external URL http://www.youtube.com/v/B4M_a7xejYI&hl=en_US&fs=1& +Blocked access to external URL http://www.youtube.com/v/laLudeUmXHM&hl=en_US&fs=1& diff --git a/chrome/common/extensions/docs/i18n.html b/chrome/common/extensions/docs/i18n.html index e07d919..b771f0e 100644 --- a/chrome/common/extensions/docs/i18n.html +++ b/chrome/common/extensions/docs/i18n.html @@ -217,8 +217,8 @@ <ol> <li> <a href="#method-getAcceptLanguages">getAcceptLanguages</a> - </li><li style="display: none; "> - <a href="#method-anchor">methodName</a> + </li><li> + <a href="#method-getMessage">getMessage</a> </li> </ol> </li> @@ -469,27 +469,105 @@ For other examples and for help in viewing the source code, see </div> <!-- /description --> - </div><div class="apiItem" style="display: none; "> - <a></a> <!-- method-anchor --> - <h4>method name</h4> + </div><div class="apiItem"> + <a name="method-getMessage"></a> <!-- method-anchor --> + <h4>getMessage</h4> - <div class="summary"><span>void</span> + <div class="summary"><span>string</span> <!-- Note: intentionally longer 80 columns --> - <span>chrome.module.methodName</span>(<span><span>, </span><span></span> - <var><span></span></var></span>)</div> + <span>chrome.i18n.getMessage</span>(<span class="null"><span style="display: none; ">, </span><span>string</span> + <var><span>messageName</span></var></span><span class="optional"><span>, </span><span>string or array of string</span> + <var><span>substitutions</span></var></span>)</div> <div class="description"> - <p class="todo">Undocumented.</p> - <p> - A description from the json schema def of the function goes here. - </p> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Get a message from the extension language catalog, for a current locale.</p> <!-- PARAMETERS --> <h4>Parameters</h4> <dl> <div> <div> - </div> + <dt> + <var>messageName</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>string</span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>Message name from the extension catalog.</dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + </div> + </div><div> + <div> + <dt> + <var>substitutions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>string or array of string</span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>1 - 9 substitution parameters, if the message requires any.</dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + </div> </div> </dl> @@ -498,12 +576,50 @@ For other examples and for help in viewing the source code, see <dl> <div> <div> - </div> + <dt> + <var style="display: none; ">paramName</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>string</span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>Message localized for current locale.</dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + </div> </div> </dl> <!-- CALLBACK --> - <div> + <div style="display: none; "> <div> <h4>Callback function</h4> <p> diff --git a/chrome/renderer/extensions/extension_api_client_unittest.cc b/chrome/renderer/extensions/extension_api_client_unittest.cc index abe3f5c..05c2ddf 100644 --- a/chrome/renderer/extensions/extension_api_client_unittest.cc +++ b/chrome/renderer/extensions/extension_api_client_unittest.cc @@ -620,7 +620,9 @@ TEST_F(ExtensionAPIClientTest, GetAcceptLanguages) { "i18n.getAcceptLanguages", "null"); } -TEST_F(ExtensionAPIClientTest, GetL10nMessage) { +// TODO(cira): re-enable when we get validation going for +// renderer_process_bindings.js +TEST_F(ExtensionAPIClientTest, DISABLED_GetL10nMessage) { ExpectJsFail("chrome.i18n.getMessage()", "Uncaught Error: Parameter 0 is required."); diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index c1009c3e..f065dd9 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -14,7 +14,6 @@ #include "base/singleton.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" -#include "chrome/common/extensions/extension_message_bundle.h" #include "chrome/common/extensions/url_pattern.h" #include "chrome/common/render_messages.h" #include "chrome/common/url_constants.h" @@ -56,13 +55,6 @@ typedef std::map<std::string, bool> PermissionsMap; // A map of extension ID to permissions map. typedef std::map<std::string, PermissionsMap> ExtensionPermissionsMap; -// 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; - const char kExtensionName[] = "chrome/ExtensionProcessBindings"; const char* kExtensionDeps[] = { BaseJsV8Extension::kName, @@ -76,7 +68,6 @@ struct SingletonData { std::set<std::string> function_names_; PageActionIdMap page_action_ids_; ExtensionPermissionsMap permissions_; - ExtensionToL10nMessagesMap extension_l10n_messages_map_; }; static std::set<std::string>* GetFunctionNameSet() { @@ -91,20 +82,6 @@ static PermissionsMap* GetPermissionsMap(const std::string& extension_id) { return &Singleton<SingletonData>()->permissions_[extension_id]; } -static ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() { - return &Singleton<SingletonData>()->extension_l10n_messages_map_; -} - -static L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) { - ExtensionToL10nMessagesMap::iterator it = - Singleton<SingletonData>()->extension_l10n_messages_map_.find(extension_id); - if (it != Singleton<SingletonData>()->extension_l10n_messages_map_.end()) { - return &(it->second); - } else { - return NULL; - } -} - static void GetActiveExtensionIDs(std::set<std::string>* extension_ids) { ExtensionPermissionsMap& permissions = Singleton<SingletonData>()->permissions_; @@ -254,8 +231,6 @@ class ExtensionImpl : public ExtensionBase { return v8::FunctionTemplate::New(StartRequest); } else if (name->Equals(v8::String::New("GetRenderViewId"))) { return v8::FunctionTemplate::New(GetRenderViewId); - } else if (name->Equals(v8::String::New("GetL10nMessage"))) { - return v8::FunctionTemplate::New(GetL10nMessage); } else if (name->Equals(v8::String::New("GetPopupView"))) { return v8::FunctionTemplate::New(GetPopupView); } else if (name->Equals(v8::String::New("GetPopupParentWindow"))) { @@ -412,71 +387,6 @@ class ExtensionImpl : public ExtensionBase { return page_action_vector; } - static v8::Handle<v8::Value> GetL10nMessage(const v8::Arguments& args) { - if (args.Length() != 2 || !args[0]->IsString()) { - NOTREACHED() << "Bad arguments"; - return v8::Undefined(); - } - - std::string extension_id = ExtensionIdForCurrentContext(); - if (extension_id.empty()) - return v8::Undefined(); - - L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id); - if (!l10n_messages) { - // Get the current RenderView so that we can send a routed IPC message - // from the correct source. - RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); - if (!renderview) - return v8::Undefined(); - - L10nMessagesMap messages; - // A sync call to load message catalogs for current extension. - renderview->Send(new ViewHostMsg_GetExtensionMessageBundle( - extension_id, &messages)); - - if (messages.empty()) - return v8::Undefined(); - - ExtensionProcessBindings::SetL10nMessages(extension_id, messages); - l10n_messages = GetL10nMessagesMap(extension_id); - if (!l10n_messages) - return v8::Undefined(); - } - - std::string message_name = *v8::String::AsciiValue(args[0]); - std::string message = - ExtensionMessageBundle::GetL10nMessage(message_name, *l10n_messages); - - std::vector<std::string> substitutions; - if (args[1]->IsNull() || args[1]->IsUndefined()) { - // chrome.i18n.getMessage("message_name"); - // chrome.i18n.getMessage("message_name", null); - return v8::String::New(message.c_str()); - } else if (args[1]->IsString()) { - // chrome.i18n.getMessage("message_name", "one param"); - std::string substitute = *v8::String::Utf8Value(args[1]->ToString()); - substitutions.push_back(substitute); - } else if (args[1]->IsArray()) { - // chrome.i18n.getMessage("message_name", ["more", "params"]); - v8::Array* placeholders = static_cast<v8::Array*>(*args[1]); - uint32_t count = placeholders->Length(); - DCHECK(count > 0 && count <= 9); - for (uint32_t i = 0; i < count; ++i) { - std::string substitute = - *v8::String::Utf8Value( - placeholders->Get(v8::Integer::New(i))->ToString()); - substitutions.push_back(substitute); - } - } else { - NOTREACHED() << "Couldn't parse second parameter."; - return v8::Undefined(); - } - - return v8::String::New(ReplaceStringPlaceholders( - message, substitutions, NULL).c_str()); - } - // Common code for starting an API request to the browser. |value_args| // contains the request's arguments. static v8::Handle<v8::Value> StartRequestCommon( @@ -656,15 +566,6 @@ void ExtensionProcessBindings::SetPageActions( } // static -void ExtensionProcessBindings::SetL10nMessages( - const std::string& extension_id, - const std::map<std::string, std::string>& l10n_messages) { - ExtensionToL10nMessagesMap& l10n_messages_map = - *GetExtensionToL10nMessagesMap(); - l10n_messages_map[extension_id] = l10n_messages; -} - -// static void ExtensionProcessBindings::SetAPIPermissions( const std::string& extension_id, const std::vector<std::string>& permissions) { diff --git a/chrome/renderer/extensions/extension_process_bindings.h b/chrome/renderer/extensions/extension_process_bindings.h index 08f068d2..2fd7613 100644 --- a/chrome/renderer/extensions/extension_process_bindings.h +++ b/chrome/renderer/extensions/extension_process_bindings.h @@ -7,7 +7,6 @@ #ifndef CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_ #define CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_ -#include <map> #include <set> #include <string> #include <vector> @@ -47,11 +46,6 @@ class ExtensionProcessBindings { static void SetHostPermissions(const GURL& extension_url, const std::vector<URLPattern>& permissions); - // Set l10n messages for a particular extension. - static void SetL10nMessages( - const std::string& extension_id, - const std::map<std::string, std::string>& l10n_messages); - // Check if the extension in the currently running context has permission to // access the given extension function. Must be called with a valid V8 // context in scope. diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc index 42882d07..54231b6 100644 --- a/chrome/renderer/extensions/renderer_extension_bindings.cc +++ b/chrome/renderer/extensions/renderer_extension_bindings.cc @@ -4,10 +4,16 @@ #include "chrome/renderer/extensions/renderer_extension_bindings.h" +#include <map> +#include <string> + #include "app/resource_bundle.h" #include "base/basictypes.h" +#include "base/singleton.h" #include "base/values.h" +#include "chrome/common/extensions/extension_message_bundle.h" #include "chrome/common/render_messages.h" +#include "chrome/common/url_constants.h" #include "chrome/renderer/extensions/bindings_utils.h" #include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/render_thread.h" @@ -29,6 +35,12 @@ 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 @@ -36,18 +48,38 @@ 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_; }; -bool HasPortData(int port_id) { + +static bool HasPortData(int port_id) { return Singleton<ExtensionData>::get()->ports.find(port_id) != Singleton<ExtensionData>::get()->ports.end(); } -ExtensionData::PortData& GetPortData(int port_id) { + +static ExtensionData::PortData& GetPortData(int port_id) { return Singleton<ExtensionData>::get()->ports[port_id]; } -void ClearPortData(int port_id) { + +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 }; @@ -72,6 +104,8 @@ class ExtensionImpl : public ExtensionBase { return v8::FunctionTemplate::New(PortAddRef); } else if (name->Equals(v8::String::New("PortRelease"))) { return v8::FunctionTemplate::New(PortRelease); + } else if (name->Equals(v8::String::New("GetL10nMessage"))) { + return v8::FunctionTemplate::New(GetL10nMessage); } return ExtensionBase::GetNativeFunction(name); } @@ -158,6 +192,81 @@ class ExtensionImpl : public ExtensionBase { } return v8::Undefined(); } + + static v8::Handle<v8::Value> GetL10nMessage(const v8::Arguments& args) { + if (args.Length() != 3 || !args[0]->IsString()) { + NOTREACHED() << "Bad arguments"; + return v8::Undefined(); + } + + std::string extension_id; + if (args[2]->IsNull() || !args[2]->IsString()) { + return v8::Undefined(); + } else { + extension_id = *v8::String::Utf8Value(args[2]->ToString()); + if (extension_id.empty()) + return v8::Undefined(); + } + + L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id); + if (!l10n_messages) { + // Get the current RenderView so that we can send a routed IPC message + // from the correct source. + RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext(); + if (!renderview) + return v8::Undefined(); + + L10nMessagesMap messages; + // A sync call to load message catalogs for current extension. + renderview->Send(new ViewHostMsg_GetExtensionMessageBundle( + extension_id, &messages)); + + if (messages.empty()) + return v8::Undefined(); + + // Save messages we got. + ExtensionToL10nMessagesMap& l10n_messages_map = + *GetExtensionToL10nMessagesMap(); + 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]); + std::string message = + ExtensionMessageBundle::GetL10nMessage(message_name, *l10n_messages); + + std::vector<std::string> substitutions; + if (args[1]->IsNull() || args[1]->IsUndefined()) { + // chrome.i18n.getMessage("message_name"); + // chrome.i18n.getMessage("message_name", null); + return v8::String::New(message.c_str()); + } else if (args[1]->IsString()) { + // chrome.i18n.getMessage("message_name", "one param"); + std::string substitute = *v8::String::Utf8Value(args[1]->ToString()); + substitutions.push_back(substitute); + } else if (args[1]->IsArray()) { + // chrome.i18n.getMessage("message_name", ["more", "params"]); + v8::Array* placeholders = static_cast<v8::Array*>(*args[1]); + uint32_t count = placeholders->Length(); + if (count <= 0 || count > 9) + return v8::Undefined(); + for (uint32_t i = 0; i < count; ++i) { + std::string substitute = + *v8::String::Utf8Value( + placeholders->Get(v8::Integer::New(i))->ToString()); + substitutions.push_back(substitute); + } + } else { + NOTREACHED() << "Couldn't parse second parameter."; + return v8::Undefined(); + } + + return v8::String::New(ReplaceStringPlaceholders( + message, substitutions, NULL).c_str()); + } }; // Convert a ListValue to a vector of V8 values. diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 1ccbd85..4558eb9 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -15,7 +15,6 @@ var chrome = chrome || {}; native function GetNextRequestId(); native function OpenChannelToTab(); native function GetRenderViewId(); - native function GetL10nMessage(); native function GetPopupParentWindow(); native function GetPopupView(); native function SetExtensionActionIcon(); @@ -427,11 +426,6 @@ var chrome = chrome || {}; return tabIdProxy; } - apiFunctions["i18n.getMessage"].handleRequest = - function(message_name, placeholders) { - return GetL10nMessage(message_name, placeholders); - } - apiFunctions["experimental.popup.show"].handleRequest = function(url, showDetails, callback) { // Second argument is a transform from HTMLElement to Rect. diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js index 5e28537..c8d2557 100644 --- a/chrome/renderer/resources/renderer_extension_bindings.js +++ b/chrome/renderer/resources/renderer_extension_bindings.js @@ -17,6 +17,7 @@ var chrome = chrome || {}; native function PortRelease(portId); native function PostMessage(portId, msg); native function GetChromeHidden(); + native function GetL10nMessage(); var chromeHidden = GetChromeHidden(); @@ -201,6 +202,11 @@ var chrome = chrome || {}; return "chrome-extension://" + extensionId + "/" + path; }; + chrome.i18n = chrome.i18n || {}; + chrome.i18n.getMessage = function(message_name, placeholders) { + return GetL10nMessage(message_name, placeholders, extensionId); + }; + if (warnOnPrivilegedApiAccess) { setupApiStubs(); } @@ -236,13 +242,14 @@ var chrome = chrome || {}; var privileged = [ // Entire namespaces. "bookmarks", "browserAction", "devtools", "experimental.extension", - "experimental.history", "experimental.popup", "i18n", "pageAction", - "pageActions", "tabs", "test", "toolstrip", "windows", + "experimental.history", "experimental.popup", "pageAction", "pageActions", + "tabs", "test", "toolstrip", "windows", // Functions/events/properties within the extension namespace. "extension.getBackgroundPage", "extension.getExtensionTabs", "extension.getToolstrips", "extension.getViews", "extension.lastError", - "extension.onConnectExternal", "extension.onRequestExternal" + "extension.onConnectExternal", "extension.onRequestExternal", + "i18n.getAcceptLanguages" ]; for (var i = 0; i < privileged.length; i++) { createStub(privileged[i]); diff --git a/chrome/test/data/extensions/api_test/i18n/a.js b/chrome/test/data/extensions/api_test/i18n/a.js new file mode 100644 index 0000000..503f8c9 --- /dev/null +++ b/chrome/test/data/extensions/api_test/i18n/a.js @@ -0,0 +1 @@ +chrome.extension.sendRequest(chrome.i18n.getMessage("message_with_one_placeholder", "19")); diff --git a/chrome/test/data/extensions/api_test/i18n/manifest.json b/chrome/test/data/extensions/api_test/i18n/manifest.json index 16e65ac..e2aa00a 100644 --- a/chrome/test/data/extensions/api_test/i18n/manifest.json +++ b/chrome/test/data/extensions/api_test/i18n/manifest.json @@ -3,5 +3,12 @@ "version": "0.1", "description": "end-to-end browser test for chrome.i18n API", "background_page": "test.html", - "default_locale": "en_US" + "permissions": ["http://*/*", "tabs"], + "default_locale": "en_US", + "content_scripts": [ + { + "matches": ["http://*/*"], + "js": ["a.js"] + } + ] } diff --git a/chrome/test/data/extensions/api_test/i18n/test.js b/chrome/test/data/extensions/api_test/i18n/test.js index 405de5b..40807f42 100644 --- a/chrome/test/data/extensions/api_test/i18n/test.js +++ b/chrome/test/data/extensions/api_test/i18n/test.js @@ -24,4 +24,16 @@ chrome.test.runTests([ chrome.test.succeed(); }, + function getMessageFromContentScript() { + chrome.extension.onRequest.addListener( + function(request, sender, sendResponse) { + chrome.test.assertEq(request, "Number of errors: 19"); + } + ); + chrome.test.log("Creating tab..."); + chrome.tabs.create({ + url: "http://localhost:1337/files/extensions/test_file.html" + }); + chrome.test.succeed(); + } ]); |