OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "extensions/renderer/messaging_bindings.h" | 5 #include "extensions/renderer/messaging_bindings.h" |
6 | 6 |
7 #include <stdint.h> | 7 #include <stdint.h> |
8 | 8 |
9 #include <map> | 9 #include <map> |
10 #include <string> | 10 #include <string> |
11 | 11 |
12 #include "base/bind.h" | 12 #include "base/bind.h" |
13 #include "base/bind_helpers.h" | 13 #include "base/bind_helpers.h" |
14 #include "base/callback.h" | 14 #include "base/callback.h" |
15 #include "base/lazy_instance.h" | |
16 #include "base/macros.h" | 15 #include "base/macros.h" |
17 #include "base/memory/weak_ptr.h" | 16 #include "base/memory/weak_ptr.h" |
18 #include "base/message_loop/message_loop.h" | 17 #include "base/message_loop/message_loop.h" |
19 #include "base/values.h" | 18 #include "base/values.h" |
20 #include "components/guest_view/common/guest_view_constants.h" | 19 #include "components/guest_view/common/guest_view_constants.h" |
21 #include "content/public/child/v8_value_converter.h" | 20 #include "content/public/child/v8_value_converter.h" |
22 #include "content/public/common/child_process_host.h" | 21 #include "content/public/common/child_process_host.h" |
23 #include "content/public/renderer/render_frame.h" | 22 #include "content/public/renderer/render_frame.h" |
24 #include "content/public/renderer/render_thread.h" | 23 #include "content/public/renderer/render_thread.h" |
25 #include "extensions/common/api/messaging/message.h" | 24 #include "extensions/common/api/messaging/message.h" |
26 #include "extensions/common/extension_messages.h" | 25 #include "extensions/common/extension_messages.h" |
27 #include "extensions/common/manifest_handlers/externally_connectable.h" | 26 #include "extensions/common/manifest_handlers/externally_connectable.h" |
28 #include "extensions/renderer/dispatcher.h" | |
29 #include "extensions/renderer/event_bindings.h" | 27 #include "extensions/renderer/event_bindings.h" |
30 #include "extensions/renderer/extension_frame_helper.h" | 28 #include "extensions/renderer/extension_frame_helper.h" |
31 #include "extensions/renderer/gc_callback.h" | 29 #include "extensions/renderer/gc_callback.h" |
32 #include "extensions/renderer/object_backed_native_handler.h" | 30 #include "extensions/renderer/object_backed_native_handler.h" |
33 #include "extensions/renderer/script_context.h" | 31 #include "extensions/renderer/script_context.h" |
34 #include "extensions/renderer/script_context_set.h" | 32 #include "extensions/renderer/script_context_set.h" |
35 #include "extensions/renderer/v8_helpers.h" | 33 #include "extensions/renderer/v8_helpers.h" |
36 #include "third_party/WebKit/public/web/WebDocument.h" | 34 #include "third_party/WebKit/public/web/WebDocument.h" |
37 #include "third_party/WebKit/public/web/WebLocalFrame.h" | 35 #include "third_party/WebKit/public/web/WebLocalFrame.h" |
38 #include "third_party/WebKit/public/web/WebScopedUserGesture.h" | 36 #include "third_party/WebKit/public/web/WebScopedUserGesture.h" |
(...skipping 15 matching lines...) Expand all Loading... | |
54 using content::V8ValueConverter; | 52 using content::V8ValueConverter; |
55 | 53 |
56 namespace extensions { | 54 namespace extensions { |
57 | 55 |
58 using v8_helpers::ToV8String; | 56 using v8_helpers::ToV8String; |
59 using v8_helpers::ToV8StringUnsafe; | 57 using v8_helpers::ToV8StringUnsafe; |
60 using v8_helpers::IsEmptyOrUndefied; | 58 using v8_helpers::IsEmptyOrUndefied; |
61 | 59 |
62 namespace { | 60 namespace { |
63 | 61 |
64 // Tracks every reference between ScriptContexts and Ports, by ID. | |
65 class PortTracker { | |
66 public: | |
67 PortTracker() {} | |
68 ~PortTracker() {} | |
69 | |
70 // Returns true if |context| references |port_id|. | |
71 bool HasReference(ScriptContext* context, int port_id) const { | |
72 auto ports = contexts_to_ports_.find(context); | |
73 return ports != contexts_to_ports_.end() && | |
74 ports->second.count(port_id) > 0; | |
75 } | |
76 | |
77 // Marks |context| and |port_id| as referencing each other. | |
78 void AddReference(ScriptContext* context, int port_id) { | |
79 contexts_to_ports_[context].insert(port_id); | |
80 } | |
81 | |
82 // Removes the references between |context| and |port_id|. | |
83 // Returns true if a reference was removed, false if the reference didn't | |
84 // exist to be removed. | |
85 bool RemoveReference(ScriptContext* context, int port_id) { | |
86 auto ports = contexts_to_ports_.find(context); | |
87 if (ports == contexts_to_ports_.end() || | |
88 ports->second.erase(port_id) == 0) { | |
89 return false; | |
90 } | |
91 if (ports->second.empty()) | |
92 contexts_to_ports_.erase(context); | |
93 return true; | |
94 } | |
95 | |
96 // Returns true if this tracker has any reference to |port_id|. | |
97 bool HasPort(int port_id) const { | |
98 for (auto it : contexts_to_ports_) { | |
99 if (it.second.count(port_id) > 0) | |
100 return true; | |
101 } | |
102 return false; | |
103 } | |
104 | |
105 // Deletes all references to |port_id|. | |
106 void DeletePort(int port_id) { | |
107 for (auto it = contexts_to_ports_.begin(); | |
108 it != contexts_to_ports_.end();) { | |
109 if (it->second.erase(port_id) > 0 && it->second.empty()) | |
110 contexts_to_ports_.erase(it++); | |
111 else | |
112 ++it; | |
113 } | |
114 } | |
115 | |
116 // Gets every port ID that has a reference to |context|. | |
117 std::set<int> GetPortsForContext(ScriptContext* context) const { | |
118 auto ports = contexts_to_ports_.find(context); | |
119 return ports == contexts_to_ports_.end() ? std::set<int>() : ports->second; | |
120 } | |
121 | |
122 private: | |
123 // Maps ScriptContexts to the port IDs that have a reference to it. | |
124 std::map<ScriptContext*, std::set<int>> contexts_to_ports_; | |
125 | |
126 DISALLOW_COPY_AND_ASSIGN(PortTracker); | |
127 }; | |
128 | |
129 base::LazyInstance<PortTracker> g_port_tracker = LAZY_INSTANCE_INITIALIZER; | |
130 | |
131 const char kPortClosedError[] = "Attempting to use a disconnected port object"; | |
132 | |
133 class ExtensionImpl : public ObjectBackedNativeHandler { | 62 class ExtensionImpl : public ObjectBackedNativeHandler { |
134 public: | 63 public: |
135 ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context) | 64 ExtensionImpl(ScriptContext* context) |
Devlin
2016/05/18 17:55:09
nit: explicit
| |
136 : ObjectBackedNativeHandler(context), | 65 : ObjectBackedNativeHandler(context), weak_ptr_factory_(this) { |
137 dispatcher_(dispatcher), | |
138 weak_ptr_factory_(this) { | |
139 RouteFunction( | 66 RouteFunction( |
140 "CloseChannel", | 67 "CloseChannel", |
141 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this))); | 68 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this))); |
142 RouteFunction( | 69 RouteFunction( |
143 "PortAddRef", | |
144 base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this))); | |
145 RouteFunction( | |
146 "PortRelease", | |
147 base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this))); | |
148 RouteFunction( | |
149 "PostMessage", | 70 "PostMessage", |
150 base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this))); | 71 base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this))); |
151 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives. | 72 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives. |
152 RouteFunction("BindToGC", | 73 RouteFunction("BindToGC", |
153 base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this))); | 74 base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this))); |
154 | |
155 // Observe |context| so that port references to it can be cleared. | |
156 context->AddInvalidationObserver(base::Bind( | |
157 &ExtensionImpl::OnContextInvalidated, weak_ptr_factory_.GetWeakPtr())); | |
158 } | 75 } |
159 | 76 |
160 ~ExtensionImpl() override {} | 77 ~ExtensionImpl() override {} |
161 | 78 |
162 private: | 79 private: |
163 void OnContextInvalidated() { | |
164 for (int port_id : g_port_tracker.Get().GetPortsForContext(context())) | |
165 ReleasePort(port_id); | |
166 } | |
167 | |
168 void ClearPortDataAndNotifyDispatcher(int port_id) { | |
169 g_port_tracker.Get().DeletePort(port_id); | |
170 dispatcher_->ClearPortData(port_id); | |
171 } | |
172 | |
173 // Sends a message along the given channel. | 80 // Sends a message along the given channel. |
174 void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) { | 81 void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) { |
175 content::RenderFrame* render_frame = context()->GetRenderFrame(); | |
176 if (!render_frame) | |
177 return; | |
178 | |
179 // Arguments are (int32_t port_id, string message). | 82 // Arguments are (int32_t port_id, string message). |
180 CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString()); | 83 CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString()); |
181 | 84 |
182 int port_id = args[0].As<v8::Int32>()->Value(); | 85 int port_id = args[0].As<v8::Int32>()->Value(); |
183 if (!g_port_tracker.Get().HasPort(port_id)) { | 86 |
184 v8::Local<v8::String> error_message = | 87 content::RenderFrame* render_frame = context()->GetRenderFrame(); |
185 ToV8StringUnsafe(args.GetIsolate(), kPortClosedError); | 88 if (render_frame) { |
186 args.GetIsolate()->ThrowException(v8::Exception::Error(error_message)); | 89 render_frame->Send(new ExtensionHostMsg_PostMessage( |
187 return; | 90 render_frame->GetRoutingID(), port_id, |
91 Message(*v8::String::Utf8Value(args[1]), | |
92 blink::WebUserGestureIndicator::isProcessingUserGesture()))); | |
188 } | 93 } |
189 | |
190 render_frame->Send(new ExtensionHostMsg_PostMessage( | |
191 render_frame->GetRoutingID(), port_id, | |
192 Message(*v8::String::Utf8Value(args[1]), | |
193 blink::WebUserGestureIndicator::isProcessingUserGesture()))); | |
194 } | 94 } |
195 | 95 |
196 // Forcefully disconnects a port. | 96 // Close a port, optionally forcefully (i.e. close the whole channel instead |
97 // of just the given port). | |
197 void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) { | 98 void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) { |
198 // Arguments are (int32_t port_id, boolean notify_browser). | 99 // Arguments are (int32_t port_id, bool force_close). |
199 CHECK_EQ(2, args.Length()); | 100 CHECK_EQ(2, args.Length()); |
200 CHECK(args[0]->IsInt32()); | 101 CHECK(args[0]->IsInt32()); |
201 CHECK(args[1]->IsBoolean()); | 102 CHECK(args[1]->IsBoolean()); |
202 | 103 |
203 int port_id = args[0].As<v8::Int32>()->Value(); | 104 int port_id = args[0].As<v8::Int32>()->Value(); |
204 if (!g_port_tracker.Get().HasPort(port_id)) | 105 bool force_close = args[1].As<v8::Boolean>()->Value(); |
205 return; | 106 ClosePort(port_id, force_close); |
206 | |
207 // Send via the RenderThread because the RenderFrame might be closing. | |
208 bool notify_browser = args[1].As<v8::Boolean>()->Value(); | |
209 content::RenderFrame* render_frame = context()->GetRenderFrame(); | |
210 if (notify_browser && render_frame) { | |
211 render_frame->Send(new ExtensionHostMsg_CloseMessagePort( | |
212 render_frame->GetRoutingID(), port_id, true)); | |
213 } | |
214 | |
215 ClearPortDataAndNotifyDispatcher(port_id); | |
216 } | |
217 | |
218 // A new port has been created for a context. This occurs both when script | |
219 // opens a connection, and when a connection is opened to this script. | |
220 void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
221 // Arguments are (int32_t port_id). | |
222 CHECK_EQ(1, args.Length()); | |
223 CHECK(args[0]->IsInt32()); | |
224 | |
225 int port_id = args[0].As<v8::Int32>()->Value(); | |
226 g_port_tracker.Get().AddReference(context(), port_id); | |
227 } | |
228 | |
229 // The frame a port lived in has been destroyed. When there are no more | |
230 // frames with a reference to a given port, we will disconnect it and notify | |
231 // the other end of the channel. | |
232 // TODO(robwu): Port lifetime management has moved to the browser, this is no | |
233 // longer needed. See .destroy_() inmessaging.js for more details. | |
234 void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
235 // Arguments are (int32_t port_id). | |
236 CHECK(args.Length() == 1 && args[0]->IsInt32()); | |
237 ReleasePort(args[0].As<v8::Int32>()->Value()); | |
238 } | |
239 | |
240 // Releases the reference to |port_id| for this context, and clears all port | |
241 // data if there are no more references. | |
242 void ReleasePort(int port_id) { | |
243 content::RenderFrame* render_frame = context()->GetRenderFrame(); | |
244 if (g_port_tracker.Get().RemoveReference(context(), port_id) && | |
245 !g_port_tracker.Get().HasPort(port_id) && render_frame) { | |
246 render_frame->Send(new ExtensionHostMsg_CloseMessagePort( | |
247 render_frame->GetRoutingID(), port_id, false)); | |
248 } | |
249 } | 107 } |
250 | 108 |
251 // void BindToGC(object, callback, port_id) | 109 // void BindToGC(object, callback, port_id) |
252 // | 110 // |
253 // Binds |callback| to be invoked *sometime after* |object| is garbage | 111 // Binds |callback| to be invoked *sometime after* |object| is garbage |
254 // collected. We don't call the method re-entrantly so as to avoid executing | 112 // collected. We don't call the method re-entrantly so as to avoid executing |
255 // JS in some bizarro undefined mid-GC state, nor do we then call into the | 113 // JS in some bizarro undefined mid-GC state, nor do we then call into the |
256 // script context if it's been invalidated. | 114 // script context if it's been invalidated. |
257 // | 115 // |
258 // If the script context *is* invalidated in the meantime, as a slight hack, | 116 // If the script context *is* invalidated in the meantime, as a slight hack, |
259 // release the port with ID |port_id| if it's >= 0. | 117 // release the port with ID |port_id| if it's >= 0. |
260 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) { | 118 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) { |
261 CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() && | 119 CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() && |
262 args[2]->IsInt32()); | 120 args[2]->IsInt32()); |
263 int port_id = args[2].As<v8::Int32>()->Value(); | 121 int port_id = args[2].As<v8::Int32>()->Value(); |
264 base::Closure fallback = base::Bind(&base::DoNothing); | 122 base::Closure fallback = base::Bind(&base::DoNothing); |
265 if (port_id >= 0) { | 123 if (port_id >= 0) { |
266 fallback = base::Bind(&ExtensionImpl::ReleasePort, | 124 // TODO(robwu): Falling back to closing the port shouldn't be needed. If |
267 weak_ptr_factory_.GetWeakPtr(), port_id); | 125 // the script context is destroyed, then the frame has navigated. But that |
126 // is already detected by the browser, so this logic is redundant. Remove | |
127 // this fallback (and move BindToGC out of messaging because it is also | |
128 // used in other places that have nothing to do with messaging...). | |
129 fallback = | |
130 base::Bind(&ExtensionImpl::ClosePort, weak_ptr_factory_.GetWeakPtr(), | |
131 port_id, false /* force_close */); | |
268 } | 132 } |
269 // Destroys itself when the object is GC'd or context is invalidated. | 133 // Destroys itself when the object is GC'd or context is invalidated. |
270 new GCCallback(context(), args[0].As<v8::Object>(), | 134 new GCCallback(context(), args[0].As<v8::Object>(), |
271 args[1].As<v8::Function>(), fallback); | 135 args[1].As<v8::Function>(), fallback); |
272 } | 136 } |
273 | 137 |
274 // Dispatcher handle. Not owned. | 138 // See ExtensionImpl::CloseChannel for documentation. |
275 Dispatcher* dispatcher_; | 139 // TODO(robwu): Merge this logic with CloseChannel once the TODO in BindToGC |
140 // has been addressed. | |
141 void ClosePort(int port_id, bool force_close) { | |
142 content::RenderFrame* render_frame = context()->GetRenderFrame(); | |
143 if (render_frame) { | |
144 render_frame->Send(new ExtensionHostMsg_CloseMessagePort( | |
145 render_frame->GetRoutingID(), port_id, force_close)); | |
146 } | |
147 } | |
276 | 148 |
277 base::WeakPtrFactory<ExtensionImpl> weak_ptr_factory_; | 149 base::WeakPtrFactory<ExtensionImpl> weak_ptr_factory_; |
278 }; | 150 }; |
279 | 151 |
152 void HasMessagePort(int port_id, | |
153 bool* has_port, | |
154 ScriptContext* script_context) { | |
155 if (*has_port) | |
156 return; // Stop checking if the port was found. | |
157 | |
158 v8::Isolate* isolate = script_context->isolate(); | |
159 v8::HandleScope handle_scope(isolate); | |
160 | |
161 v8::Local<v8::Value> port_id_handle = v8::Integer::New(isolate, port_id); | |
162 v8::Local<v8::Value> v8_has_port = | |
163 script_context->module_system()->CallModuleMethod("messaging", "hasPort", | |
164 1, &port_id_handle); | |
165 if (IsEmptyOrUndefied(v8_has_port)) | |
166 return; | |
167 CHECK(v8_has_port->IsBoolean()); | |
168 if (!v8_has_port.As<v8::Boolean>()->Value()) | |
169 return; | |
170 *has_port = true; | |
171 } | |
172 | |
280 void DispatchOnConnectToScriptContext( | 173 void DispatchOnConnectToScriptContext( |
281 int target_port_id, | 174 int target_port_id, |
282 const std::string& channel_name, | 175 const std::string& channel_name, |
283 const ExtensionMsg_TabConnectionInfo* source, | 176 const ExtensionMsg_TabConnectionInfo* source, |
284 const ExtensionMsg_ExternalConnectionInfo& info, | 177 const ExtensionMsg_ExternalConnectionInfo& info, |
285 const std::string& tls_channel_id, | 178 const std::string& tls_channel_id, |
286 bool* port_created, | 179 bool* port_created, |
287 ScriptContext* script_context) { | 180 ScriptContext* script_context) { |
288 v8::Isolate* isolate = script_context->isolate(); | 181 v8::Isolate* isolate = script_context->isolate(); |
289 v8::HandleScope handle_scope(isolate); | 182 v8::HandleScope handle_scope(isolate); |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
428 } else { | 321 } else { |
429 arguments.push_back(v8::Null(isolate)); | 322 arguments.push_back(v8::Null(isolate)); |
430 } | 323 } |
431 | 324 |
432 script_context->module_system()->CallModuleMethod( | 325 script_context->module_system()->CallModuleMethod( |
433 "messaging", "dispatchOnDisconnect", &arguments); | 326 "messaging", "dispatchOnDisconnect", &arguments); |
434 } | 327 } |
435 | 328 |
436 } // namespace | 329 } // namespace |
437 | 330 |
438 ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher, | 331 ObjectBackedNativeHandler* MessagingBindings::Get(ScriptContext* context) { |
439 ScriptContext* context) { | 332 return new ExtensionImpl(context); |
440 return new ExtensionImpl(dispatcher, context); | 333 } |
334 | |
335 void MessagingBindings::ValidateMessagePort( | |
336 const ScriptContextSet& context_set, | |
337 int port_id, | |
338 content::RenderFrame* render_frame) { | |
339 int routing_id = render_frame->GetRoutingID(); | |
340 | |
341 bool has_port = false; | |
342 context_set.ForEach(render_frame, | |
343 base::Bind(&HasMessagePort, port_id, &has_port)); | |
344 // Note: HasMessagePort invokes a JavaScript function. If the runtime of the | |
345 // extension bindings in JS have been compromised, then |render_frame| may be | |
346 // invalid at this point. | |
347 | |
348 // A reply is only sent if the port is missing, because the port is assumed to | |
349 // exist unless stated otherwise. | |
350 if (!has_port) { | |
351 content::RenderThread::Get()->Send( | |
352 new ExtensionHostMsg_CloseMessagePort(routing_id, port_id, false)); | |
353 } | |
441 } | 354 } |
442 | 355 |
443 // static | 356 // static |
444 void MessagingBindings::DispatchOnConnect( | 357 void MessagingBindings::DispatchOnConnect( |
445 const ScriptContextSet& context_set, | 358 const ScriptContextSet& context_set, |
446 int target_port_id, | 359 int target_port_id, |
447 const std::string& channel_name, | 360 const std::string& channel_name, |
448 const ExtensionMsg_TabConnectionInfo& source, | 361 const ExtensionMsg_TabConnectionInfo& source, |
449 const ExtensionMsg_ExternalConnectionInfo& info, | 362 const ExtensionMsg_ExternalConnectionInfo& info, |
450 const std::string& tls_channel_id, | 363 const std::string& tls_channel_id, |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
484 const ScriptContextSet& context_set, | 397 const ScriptContextSet& context_set, |
485 int port_id, | 398 int port_id, |
486 const std::string& error_message, | 399 const std::string& error_message, |
487 content::RenderFrame* restrict_to_render_frame) { | 400 content::RenderFrame* restrict_to_render_frame) { |
488 context_set.ForEach( | 401 context_set.ForEach( |
489 restrict_to_render_frame, | 402 restrict_to_render_frame, |
490 base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message)); | 403 base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message)); |
491 } | 404 } |
492 | 405 |
493 } // namespace extensions | 406 } // namespace extensions |
OLD | NEW |