Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(210)

Side by Side Diff: extensions/renderer/messaging_bindings.cc

Issue 1966283002: Remove port lifetime management from renderer (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Devlin's nits up to #10 Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698