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 |