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

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

Issue 1136953017: Add fallback mechanism to release Extension ports if the JS context has been destroyed. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: tweak Created 5 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
« no previous file with comments | « no previous file | extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <map> 7 #include <map>
8 #include <string> 8 #include <string>
9 9
10 #include "base/basictypes.h" 10 #include "base/basictypes.h"
11 #include "base/bind.h" 11 #include "base/bind.h"
12 #include "base/bind_helpers.h" 12 #include "base/bind_helpers.h"
13 #include "base/callback.h"
13 #include "base/lazy_instance.h" 14 #include "base/lazy_instance.h"
15 #include "base/memory/weak_ptr.h"
14 #include "base/message_loop/message_loop.h" 16 #include "base/message_loop/message_loop.h"
15 #include "base/values.h" 17 #include "base/values.h"
16 #include "components/guest_view/common/guest_view_constants.h" 18 #include "components/guest_view/common/guest_view_constants.h"
17 #include "content/public/child/v8_value_converter.h" 19 #include "content/public/child/v8_value_converter.h"
18 #include "content/public/common/child_process_host.h" 20 #include "content/public/common/child_process_host.h"
19 #include "content/public/renderer/render_frame.h" 21 #include "content/public/renderer/render_frame.h"
20 #include "content/public/renderer/render_thread.h" 22 #include "content/public/renderer/render_thread.h"
21 #include "extensions/common/api/messaging/message.h" 23 #include "extensions/common/api/messaging/message.h"
22 #include "extensions/common/extension_messages.h" 24 #include "extensions/common/extension_messages.h"
23 #include "extensions/common/manifest_handlers/externally_connectable.h" 25 #include "extensions/common/manifest_handlers/externally_connectable.h"
(...skipping 20 matching lines...) Expand all
44 // port.postMessage('I got your reponse'); 46 // port.postMessage('I got your reponse');
45 // }); 47 // });
46 48
47 using content::RenderThread; 49 using content::RenderThread;
48 using content::V8ValueConverter; 50 using content::V8ValueConverter;
49 51
50 namespace extensions { 52 namespace extensions {
51 53
52 namespace { 54 namespace {
53 55
56 // Binds |callback| to run when |object| is garbage collected. So as to not
57 // re-entrantly call into v8, we execute this function asynchronously, at
58 // which point |context| may have been invalidated. If so, |callback| is not
59 // run, and |fallback| will be called instead.
60 //
61 // Deletes itself when the object args[0] is garbage collected or when the
62 // context is invalidated.
63 class GCCallback : public base::SupportsWeakPtr<GCCallback> {
not at google - send to devlin 2015/05/15 18:14:24 Moved this up here because that's where it belongs
64 public:
65 GCCallback(ScriptContext* context,
66 const v8::Local<v8::Object>& object,
67 const v8::Local<v8::Function>& callback,
68 const base::Closure& fallback)
69 : context_(context),
70 object_(context->isolate(), object),
71 callback_(context->isolate(), callback),
72 fallback_(fallback) {
73 object_.SetWeak(this, FirstWeakCallback, v8::WeakCallbackType::kParameter);
74 context->AddInvalidationObserver(
not at google - send to devlin 2015/05/15 18:14:24 Now watches for context invalidation.
75 base::Bind(&GCCallback::OnContextInvalidated, AsWeakPtr()));
76 }
77
78 private:
79 static void FirstWeakCallback(const v8::WeakCallbackInfo<GCCallback>& data) {
80 // v8 says we need to explicitly reset weak handles from their callbacks.
81 // It's not implicit as one might expect.
82 data.GetParameter()->object_.Reset();
83 data.SetSecondPassCallback(SecondWeakCallback);
84 }
85
86 static void SecondWeakCallback(const v8::WeakCallbackInfo<GCCallback>& data) {
87 base::MessageLoop::current()->PostTask(
88 FROM_HERE,
89 base::Bind(&GCCallback::RunCallback, data.GetParameter()->AsWeakPtr()));
not at google - send to devlin 2015/05/15 18:14:24 Changed this from being base::Owned to a WeakPtr w
90 }
91
92 void RunCallback() {
93 CHECK(context_);
94 v8::Isolate* isolate = context_->isolate();
95 v8::HandleScope handle_scope(isolate);
96 context_->CallFunction(v8::Local<v8::Function>::New(isolate, callback_));
not at google - send to devlin 2015/05/15 18:14:24 Now uses the proper CallFunction method on context
97 delete this;
98 }
99
100 void OnContextInvalidated() {
101 fallback_.Run();
102 context_ = NULL;
103 delete this;
104 }
105
106 // ScriptContext which owns this GCCallback.
107 ScriptContext* context_;
108
109 // Holds a global handle to the object this GCCallback is bound to.
110 v8::Global<v8::Object> object_;
111
112 // Function to run when |object_| bound to this GCCallback is GC'd.
113 v8::Global<v8::Function> callback_;
114
115 // Function to run if context is invalidated before we have a chance
116 // to execute |callback_|.
117 base::Closure fallback_;
118
119 DISALLOW_COPY_AND_ASSIGN(GCCallback);
120 };
121
54 struct ExtensionData { 122 struct ExtensionData {
55 struct PortData { 123 struct PortData {
56 int ref_count; // how many contexts have a handle to this port 124 int ref_count; // how many contexts have a handle to this port
57 PortData() : ref_count(0) {} 125 PortData() : ref_count(0) {}
58 }; 126 };
59 std::map<int, PortData> ports; // port ID -> data 127 std::map<int, PortData> ports; // port ID -> data
60 }; 128 };
61 129
62 base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER; 130 base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER;
63 131
(...skipping 10 matching lines...) Expand all
74 g_extension_data.Get().ports.erase(port_id); 142 g_extension_data.Get().ports.erase(port_id);
75 } 143 }
76 144
77 const char kPortClosedError[] = "Attempting to use a disconnected port object"; 145 const char kPortClosedError[] = "Attempting to use a disconnected port object";
78 const char kReceivingEndDoesntExistError[] = 146 const char kReceivingEndDoesntExistError[] =
79 "Could not establish connection. Receiving end does not exist."; 147 "Could not establish connection. Receiving end does not exist.";
80 148
81 class ExtensionImpl : public ObjectBackedNativeHandler { 149 class ExtensionImpl : public ObjectBackedNativeHandler {
82 public: 150 public:
83 ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context) 151 ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context)
84 : ObjectBackedNativeHandler(context), dispatcher_(dispatcher) { 152 : ObjectBackedNativeHandler(context),
153 dispatcher_(dispatcher),
154 weak_ptr_factory_(this) {
85 RouteFunction( 155 RouteFunction(
86 "CloseChannel", 156 "CloseChannel",
87 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this))); 157 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
88 RouteFunction( 158 RouteFunction(
89 "PortAddRef", 159 "PortAddRef",
90 base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this))); 160 base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
91 RouteFunction( 161 RouteFunction(
92 "PortRelease", 162 "PortRelease",
93 base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this))); 163 base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
94 RouteFunction( 164 RouteFunction(
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
159 229
160 int port_id = args[0]->Int32Value(); 230 int port_id = args[0]->Int32Value();
161 ++GetPortData(port_id).ref_count; 231 ++GetPortData(port_id).ref_count;
162 } 232 }
163 233
164 // The frame a port lived in has been destroyed. When there are no more 234 // 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 235 // frames with a reference to a given port, we will disconnect it and notify
166 // the other end of the channel. 236 // the other end of the channel.
167 void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) { 237 void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
168 // Arguments are (int32 port_id). 238 // Arguments are (int32 port_id).
169 CHECK_EQ(1, args.Length()); 239 CHECK(args.Length() == 1 && args[0]->IsInt32());
170 CHECK(args[0]->IsInt32()); 240 ReleasePort(args[0]->Int32Value());
241 }
171 242
172 int port_id = args[0]->Int32Value(); 243 // Implementation of both the PortRelease native handler call, and callback
244 // when contexts are invalidated to release their ports.
245 void ReleasePort(int port_id) {
173 if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) { 246 if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) {
174 // Send via the RenderThread because the RenderFrame might be closing. 247 // Send via the RenderThread because the RenderFrame might be closing.
175 content::RenderThread::Get()->Send( 248 content::RenderThread::Get()->Send(
176 new ExtensionHostMsg_CloseChannel(port_id, std::string())); 249 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
177 ClearPortDataAndNotifyDispatcher(port_id); 250 ClearPortDataAndNotifyDispatcher(port_id);
178 } 251 }
179 } 252 }
180 253
181 // Holds a |callback| to run sometime after |object| is GC'ed. |callback| will 254 // void BindToGC(object, callback, port_id)
182 // not be executed re-entrantly to avoid running JS in an unexpected state.
183 class GCCallback {
184 public:
185 static void Bind(v8::Local<v8::Object> object,
186 v8::Local<v8::Function> callback,
187 v8::Isolate* isolate) {
188 GCCallback* cb = new GCCallback(object, callback, isolate);
189 cb->object_.SetWeak(cb, FirstWeakCallback,
190 v8::WeakCallbackType::kParameter);
191 }
192
193 private:
194 static void FirstWeakCallback(
195 const v8::WeakCallbackInfo<GCCallback>& data) {
196 // v8 says we need to explicitly reset weak handles from their callbacks.
197 // It's not implicit as one might expect.
198 data.GetParameter()->object_.Reset();
199 data.SetSecondPassCallback(SecondWeakCallback);
200 }
201
202 static void SecondWeakCallback(
203 const v8::WeakCallbackInfo<GCCallback>& data) {
204 base::MessageLoop::current()->PostTask(
205 FROM_HERE,
206 base::Bind(&GCCallback::RunCallback,
207 base::Owned(data.GetParameter())));
208 }
209
210 GCCallback(v8::Local<v8::Object> object,
211 v8::Local<v8::Function> callback,
212 v8::Isolate* isolate)
213 : object_(isolate, object),
214 callback_(isolate, callback),
215 isolate_(isolate) {}
216
217 void RunCallback() {
218 v8::HandleScope handle_scope(isolate_);
219 v8::Local<v8::Function> callback =
220 v8::Local<v8::Function>::New(isolate_, callback_);
221 v8::Local<v8::Context> context = callback->CreationContext();
222 if (context.IsEmpty())
223 return;
224 v8::Context::Scope context_scope(context);
225 blink::WebScopedMicrotaskSuppression suppression;
226 callback->Call(context->Global(), 0, NULL);
227 }
228
229 v8::Global<v8::Object> object_;
230 v8::Global<v8::Function> callback_;
231 v8::Isolate* isolate_;
232
233 DISALLOW_COPY_AND_ASSIGN(GCCallback);
234 };
235
236 // void BindToGC(object, callback)
237 // 255 //
238 // Binds |callback| to be invoked *sometime after* |object| is garbage 256 // Binds |callback| to be invoked *sometime after* |object| is garbage
239 // collected. We don't call the method re-entrantly so as to avoid executing 257 // collected. We don't call the method re-entrantly so as to avoid executing
240 // JS in some bizarro undefined mid-GC state. 258 // JS in some bizarro undefined mid-GC state, nor do we then call into the
259 // script context if it's been invalidated.
260 //
261 // If the script context *is* invalidated in the meantime, as a slight hack,
262 // release the port with ID |port_id| if it's >= 0.
241 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) { 263 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
242 CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction()); 264 CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() &&
243 GCCallback::Bind(args[0].As<v8::Object>(), 265 args[2]->IsInt32());
244 args[1].As<v8::Function>(), 266 int port_id = args[2]->Int32Value();
245 args.GetIsolate()); 267 base::Closure fallback = base::Bind(&base::DoNothing);
268 if (port_id >= 0) {
not at google - send to devlin 2015/05/15 18:14:24 The hack in question.
269 fallback = base::Bind(&ExtensionImpl::ReleasePort,
270 weak_ptr_factory_.GetWeakPtr(), port_id);
271 }
272 new GCCallback(context(), args[0].As<v8::Object>(),
Ken Rockot(use gerrit already) 2015/05/15 21:47:54 nit: maybe document that this kills itself
not at google - send to devlin 2015/05/15 21:53:12 Done.
273 args[1].As<v8::Function>(), fallback);
246 } 274 }
247 275
248 // Dispatcher handle. Not owned. 276 // Dispatcher handle. Not owned.
249 Dispatcher* dispatcher_; 277 Dispatcher* dispatcher_;
278
279 base::WeakPtrFactory<ExtensionImpl> weak_ptr_factory_;
250 }; 280 };
251 281
252 void DispatchOnConnectToScriptContext( 282 void DispatchOnConnectToScriptContext(
253 int target_port_id, 283 int target_port_id,
254 const std::string& channel_name, 284 const std::string& channel_name,
255 const ExtensionMsg_TabConnectionInfo* source, 285 const ExtensionMsg_TabConnectionInfo* source,
256 const ExtensionMsg_ExternalConnectionInfo& info, 286 const ExtensionMsg_ExternalConnectionInfo& info,
257 const std::string& tls_channel_id, 287 const std::string& tls_channel_id,
258 bool* port_created, 288 bool* port_created,
259 ScriptContext* script_context) { 289 ScriptContext* script_context) {
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after
453 // TODO(robwu): ScriptContextSet.ForEach should accept RenderFrame*. 483 // TODO(robwu): ScriptContextSet.ForEach should accept RenderFrame*.
454 content::RenderView* restrict_to_render_view = 484 content::RenderView* restrict_to_render_view =
455 restrict_to_render_frame ? restrict_to_render_frame->GetRenderView() 485 restrict_to_render_frame ? restrict_to_render_frame->GetRenderView()
456 : NULL; 486 : NULL;
457 context_set.ForEach( 487 context_set.ForEach(
458 restrict_to_render_view, 488 restrict_to_render_view,
459 base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message)); 489 base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message));
460 } 490 }
461 491
462 } // namespace extensions 492 } // namespace extensions
OLDNEW
« no previous file with comments | « no previous file | extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698