| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/renderer/extensions/renderer_extension_bindings.h" | |
| 6 | |
| 7 #include <map> | |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/basictypes.h" | |
| 11 #include "base/lazy_instance.h" | |
| 12 #include "chrome/common/extensions/extension_message_bundle.h" | |
| 13 #include "chrome/common/extensions/extension_messages.h" | |
| 14 #include "chrome/common/url_constants.h" | |
| 15 #include "chrome/renderer/extensions/chrome_v8_context.h" | |
| 16 #include "chrome/renderer/extensions/chrome_v8_context_set.h" | |
| 17 #include "chrome/renderer/extensions/chrome_v8_extension.h" | |
| 18 #include "chrome/renderer/extensions/event_bindings.h" | |
| 19 #include "chrome/renderer/extensions/extension_dispatcher.h" | |
| 20 #include "content/public/renderer/render_thread.h" | |
| 21 #include "content/public/renderer/render_view.h" | |
| 22 #include "grit/renderer_resources.h" | |
| 23 #include "v8/include/v8.h" | |
| 24 | |
| 25 // Message passing API example (in a content script): | |
| 26 // var extension = | |
| 27 // new chrome.Extension('00123456789abcdef0123456789abcdef0123456'); | |
| 28 // var port = extension.connect(); | |
| 29 // port.postMessage('Can you hear me now?'); | |
| 30 // port.onmessage.addListener(function(msg, port) { | |
| 31 // alert('response=' + msg); | |
| 32 // port.postMessage('I got your reponse'); | |
| 33 // }); | |
| 34 | |
| 35 using content::RenderThread; | |
| 36 | |
| 37 namespace { | |
| 38 | |
| 39 struct ExtensionData { | |
| 40 struct PortData { | |
| 41 int ref_count; // how many contexts have a handle to this port | |
| 42 bool disconnected; // true if this port was forcefully disconnected | |
| 43 PortData() : ref_count(0), disconnected(false) {} | |
| 44 }; | |
| 45 std::map<int, PortData> ports; // port ID -> data | |
| 46 }; | |
| 47 | |
| 48 static base::LazyInstance<ExtensionData> g_extension_data( | |
| 49 base::LINKER_INITIALIZED); | |
| 50 | |
| 51 static bool HasPortData(int port_id) { | |
| 52 return g_extension_data.Get().ports.find(port_id) != | |
| 53 g_extension_data.Get().ports.end(); | |
| 54 } | |
| 55 | |
| 56 static ExtensionData::PortData& GetPortData(int port_id) { | |
| 57 return g_extension_data.Get().ports[port_id]; | |
| 58 } | |
| 59 | |
| 60 static void ClearPortData(int port_id) { | |
| 61 g_extension_data.Get().ports.erase(port_id); | |
| 62 } | |
| 63 | |
| 64 const char kPortClosedError[] = "Attempting to use a disconnected port object"; | |
| 65 const char* kExtensionDeps[] = { "extensions/event.js" }; | |
| 66 | |
| 67 class ExtensionImpl : public ChromeV8Extension { | |
| 68 public: | |
| 69 explicit ExtensionImpl(ExtensionDispatcher* dispatcher) | |
| 70 : ChromeV8Extension("extensions/renderer_extension_bindings.js", | |
| 71 IDR_RENDERER_EXTENSION_BINDINGS_JS, | |
| 72 arraysize(kExtensionDeps), kExtensionDeps, | |
| 73 dispatcher) { | |
| 74 } | |
| 75 ~ExtensionImpl() {} | |
| 76 | |
| 77 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( | |
| 78 v8::Handle<v8::String> name) { | |
| 79 if (name->Equals(v8::String::New("OpenChannelToExtension"))) { | |
| 80 return v8::FunctionTemplate::New(OpenChannelToExtension); | |
| 81 } else if (name->Equals(v8::String::New("PostMessage"))) { | |
| 82 return v8::FunctionTemplate::New(PostMessage); | |
| 83 } else if (name->Equals(v8::String::New("CloseChannel"))) { | |
| 84 return v8::FunctionTemplate::New(CloseChannel); | |
| 85 } else if (name->Equals(v8::String::New("PortAddRef"))) { | |
| 86 return v8::FunctionTemplate::New(PortAddRef); | |
| 87 } else if (name->Equals(v8::String::New("PortRelease"))) { | |
| 88 return v8::FunctionTemplate::New(PortRelease); | |
| 89 } else if (name->Equals(v8::String::New("GetL10nMessage"))) { | |
| 90 return v8::FunctionTemplate::New(GetL10nMessage); | |
| 91 } | |
| 92 return ChromeV8Extension::GetNativeFunction(name); | |
| 93 } | |
| 94 | |
| 95 // Creates a new messaging channel to the given extension. | |
| 96 static v8::Handle<v8::Value> OpenChannelToExtension( | |
| 97 const v8::Arguments& args) { | |
| 98 // Get the current RenderView so that we can send a routed IPC message from | |
| 99 // the correct source. | |
| 100 content::RenderView* renderview = GetCurrentRenderView(); | |
| 101 if (!renderview) | |
| 102 return v8::Undefined(); | |
| 103 | |
| 104 if (args.Length() >= 3 && args[0]->IsString() && args[1]->IsString() && | |
| 105 args[2]->IsString()) { | |
| 106 std::string source_id = *v8::String::Utf8Value(args[0]->ToString()); | |
| 107 std::string target_id = *v8::String::Utf8Value(args[1]->ToString()); | |
| 108 std::string channel_name = *v8::String::Utf8Value(args[2]->ToString()); | |
| 109 int port_id = -1; | |
| 110 renderview->Send(new ExtensionHostMsg_OpenChannelToExtension( | |
| 111 renderview->GetRoutingId(), source_id, target_id, | |
| 112 channel_name, &port_id)); | |
| 113 return v8::Integer::New(port_id); | |
| 114 } | |
| 115 return v8::Undefined(); | |
| 116 } | |
| 117 | |
| 118 // Sends a message along the given channel. | |
| 119 static v8::Handle<v8::Value> PostMessage(const v8::Arguments& args) { | |
| 120 content::RenderView* renderview = GetCurrentRenderView(); | |
| 121 if (!renderview) | |
| 122 return v8::Undefined(); | |
| 123 | |
| 124 if (args.Length() >= 2 && args[0]->IsInt32() && args[1]->IsString()) { | |
| 125 int port_id = args[0]->Int32Value(); | |
| 126 if (!HasPortData(port_id)) { | |
| 127 return v8::ThrowException(v8::Exception::Error( | |
| 128 v8::String::New(kPortClosedError))); | |
| 129 } | |
| 130 std::string message = *v8::String::Utf8Value(args[1]->ToString()); | |
| 131 renderview->Send(new ExtensionHostMsg_PostMessage( | |
| 132 renderview->GetRoutingId(), port_id, message)); | |
| 133 } | |
| 134 return v8::Undefined(); | |
| 135 } | |
| 136 | |
| 137 // Forcefully disconnects a port. | |
| 138 static v8::Handle<v8::Value> CloseChannel(const v8::Arguments& args) { | |
| 139 if (args.Length() >= 2 && args[0]->IsInt32() && args[1]->IsBoolean()) { | |
| 140 int port_id = args[0]->Int32Value(); | |
| 141 if (!HasPortData(port_id)) { | |
| 142 return v8::Undefined(); | |
| 143 } | |
| 144 // Send via the RenderThread because the RenderView might be closing. | |
| 145 bool notify_browser = args[1]->BooleanValue(); | |
| 146 if (notify_browser) | |
| 147 content::RenderThread::Get()->Send( | |
| 148 new ExtensionHostMsg_CloseChannel(port_id)); | |
| 149 ClearPortData(port_id); | |
| 150 } | |
| 151 return v8::Undefined(); | |
| 152 } | |
| 153 | |
| 154 // A new port has been created for a context. This occurs both when script | |
| 155 // opens a connection, and when a connection is opened to this script. | |
| 156 static v8::Handle<v8::Value> PortAddRef(const v8::Arguments& args) { | |
| 157 if (args.Length() >= 1 && args[0]->IsInt32()) { | |
| 158 int port_id = args[0]->Int32Value(); | |
| 159 ++GetPortData(port_id).ref_count; | |
| 160 } | |
| 161 return v8::Undefined(); | |
| 162 } | |
| 163 | |
| 164 // The frame a port lived in has been destroyed. When there are no more | |
| 165 // frames with a reference to a given port, we will disconnect it and notify | |
| 166 // the other end of the channel. | |
| 167 static v8::Handle<v8::Value> PortRelease(const v8::Arguments& args) { | |
| 168 if (args.Length() >= 1 && args[0]->IsInt32()) { | |
| 169 int port_id = args[0]->Int32Value(); | |
| 170 if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) { | |
| 171 // Send via the RenderThread because the RenderView might be closing. | |
| 172 content::RenderThread::Get()->Send( | |
| 173 new ExtensionHostMsg_CloseChannel(port_id)); | |
| 174 ClearPortData(port_id); | |
| 175 } | |
| 176 } | |
| 177 return v8::Undefined(); | |
| 178 } | |
| 179 | |
| 180 static v8::Handle<v8::Value> GetL10nMessage(const v8::Arguments& args) { | |
| 181 if (args.Length() != 3 || !args[0]->IsString()) { | |
| 182 NOTREACHED() << "Bad arguments"; | |
| 183 return v8::Undefined(); | |
| 184 } | |
| 185 | |
| 186 std::string extension_id; | |
| 187 if (args[2]->IsNull() || !args[2]->IsString()) { | |
| 188 return v8::Undefined(); | |
| 189 } else { | |
| 190 extension_id = *v8::String::Utf8Value(args[2]->ToString()); | |
| 191 if (extension_id.empty()) | |
| 192 return v8::Undefined(); | |
| 193 } | |
| 194 | |
| 195 L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id); | |
| 196 if (!l10n_messages) { | |
| 197 // Get the current RenderView so that we can send a routed IPC message | |
| 198 // from the correct source. | |
| 199 content::RenderView* renderview = GetCurrentRenderView(); | |
| 200 if (!renderview) | |
| 201 return v8::Undefined(); | |
| 202 | |
| 203 L10nMessagesMap messages; | |
| 204 // A sync call to load message catalogs for current extension. | |
| 205 renderview->Send(new ExtensionHostMsg_GetMessageBundle( | |
| 206 extension_id, &messages)); | |
| 207 | |
| 208 // Save messages we got. | |
| 209 ExtensionToL10nMessagesMap& l10n_messages_map = | |
| 210 *GetExtensionToL10nMessagesMap(); | |
| 211 l10n_messages_map[extension_id] = messages; | |
| 212 | |
| 213 l10n_messages = GetL10nMessagesMap(extension_id); | |
| 214 } | |
| 215 | |
| 216 std::string message_name = *v8::String::AsciiValue(args[0]); | |
| 217 std::string message = | |
| 218 ExtensionMessageBundle::GetL10nMessage(message_name, *l10n_messages); | |
| 219 | |
| 220 std::vector<std::string> substitutions; | |
| 221 if (args[1]->IsNull() || args[1]->IsUndefined()) { | |
| 222 // chrome.i18n.getMessage("message_name"); | |
| 223 // chrome.i18n.getMessage("message_name", null); | |
| 224 return v8::String::New(message.c_str()); | |
| 225 } else if (args[1]->IsString()) { | |
| 226 // chrome.i18n.getMessage("message_name", "one param"); | |
| 227 std::string substitute = *v8::String::Utf8Value(args[1]->ToString()); | |
| 228 substitutions.push_back(substitute); | |
| 229 } else if (args[1]->IsArray()) { | |
| 230 // chrome.i18n.getMessage("message_name", ["more", "params"]); | |
| 231 v8::Local<v8::Array> placeholders = v8::Local<v8::Array>::Cast(args[1]); | |
| 232 uint32_t count = placeholders->Length(); | |
| 233 if (count <= 0 || count > 9) | |
| 234 return v8::Undefined(); | |
| 235 for (uint32_t i = 0; i < count; ++i) { | |
| 236 std::string substitute = | |
| 237 *v8::String::Utf8Value( | |
| 238 placeholders->Get(v8::Integer::New(i))->ToString()); | |
| 239 substitutions.push_back(substitute); | |
| 240 } | |
| 241 } else { | |
| 242 NOTREACHED() << "Couldn't parse second parameter."; | |
| 243 return v8::Undefined(); | |
| 244 } | |
| 245 | |
| 246 return v8::String::New(ReplaceStringPlaceholders( | |
| 247 message, substitutions, NULL).c_str()); | |
| 248 } | |
| 249 }; | |
| 250 | |
| 251 } // namespace | |
| 252 | |
| 253 v8::Extension* RendererExtensionBindings::Get(ExtensionDispatcher* dispatcher) { | |
| 254 static v8::Extension* extension = new ExtensionImpl(dispatcher); | |
| 255 return extension; | |
| 256 } | |
| 257 | |
| 258 void RendererExtensionBindings::DeliverMessage( | |
| 259 const ChromeV8ContextSet::ContextSet& contexts, | |
| 260 int target_port_id, | |
| 261 const std::string& message, | |
| 262 content::RenderView* restrict_to_render_view) { | |
| 263 v8::HandleScope handle_scope; | |
| 264 | |
| 265 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin(); | |
| 266 it != contexts.end(); ++it) { | |
| 267 | |
| 268 if (restrict_to_render_view && | |
| 269 restrict_to_render_view != (*it)->GetRenderView()) { | |
| 270 continue; | |
| 271 } | |
| 272 | |
| 273 // Check to see whether the context has this port before bothering to create | |
| 274 // the message. | |
| 275 v8::Handle<v8::Value> port_id_handle = v8::Integer::New(target_port_id); | |
| 276 v8::Handle<v8::Value> has_port; | |
| 277 if (!(*it)->CallChromeHiddenMethod("Port.hasPort", 1, &port_id_handle, | |
| 278 &has_port)) { | |
| 279 continue; | |
| 280 } | |
| 281 | |
| 282 CHECK(!has_port.IsEmpty()); | |
| 283 if (!has_port->BooleanValue()) | |
| 284 continue; | |
| 285 | |
| 286 std::vector<v8::Handle<v8::Value> > arguments; | |
| 287 arguments.push_back(v8::String::New(message.c_str(), message.size())); | |
| 288 arguments.push_back(port_id_handle); | |
| 289 CHECK((*it)->CallChromeHiddenMethod("Port.dispatchOnMessage", | |
| 290 arguments.size(), | |
| 291 &arguments[0], | |
| 292 NULL)); | |
| 293 } | |
| 294 } | |
| OLD | NEW |