OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/browser/extensions/extension_message_service.h" | |
6 | |
7 #include "base/atomic_sequence_num.h" | |
8 #include "base/bind.h" | |
9 #include "base/callback.h" | |
10 #include "base/json/json_writer.h" | |
11 #include "base/stl_util.h" | |
12 #include "base/values.h" | |
13 #include "chrome/browser/extensions/extension_host.h" | |
14 #include "chrome/browser/extensions/extension_process_manager.h" | |
15 #include "chrome/browser/extensions/extension_service.h" | |
16 #include "chrome/browser/extensions/extension_system.h" | |
17 #include "chrome/browser/extensions/extension_tab_util.h" | |
18 #include "chrome/browser/extensions/lazy_background_task_queue.h" | |
19 #include "chrome/browser/extensions/process_map.h" | |
20 #include "chrome/browser/profiles/profile.h" | |
21 #include "chrome/browser/tab_contents/tab_util.h" | |
22 #include "chrome/browser/ui/tab_contents/tab_contents.h" | |
23 #include "chrome/common/chrome_notification_types.h" | |
24 #include "chrome/common/extensions/extension.h" | |
25 #include "chrome/common/extensions/extension_messages.h" | |
26 #include "chrome/common/view_type.h" | |
27 #include "content/public/browser/notification_service.h" | |
28 #include "content/public/browser/render_process_host.h" | |
29 #include "content/public/browser/render_view_host.h" | |
30 #include "content/public/browser/site_instance.h" | |
31 #include "content/public/browser/web_contents.h" | |
32 | |
33 using content::SiteInstance; | |
34 using content::WebContents; | |
35 | |
36 // Since we have 2 ports for every channel, we just index channels by half the | |
37 // port ID. | |
38 #define GET_CHANNEL_ID(port_id) ((port_id) / 2) | |
39 #define GET_CHANNEL_OPENER_ID(channel_id) ((channel_id) * 2) | |
40 #define GET_CHANNEL_RECEIVERS_ID(channel_id) ((channel_id) * 2 + 1) | |
41 | |
42 // Port1 is always even, port2 is always odd. | |
43 #define IS_OPENER_PORT_ID(port_id) (((port_id) & 1) == 0) | |
44 | |
45 // Change even to odd and vice versa, to get the other side of a given channel. | |
46 #define GET_OPPOSITE_PORT_ID(source_port_id) ((source_port_id) ^ 1) | |
47 | |
48 struct ExtensionMessageService::MessagePort { | |
49 content::RenderProcessHost* process; | |
50 int routing_id; | |
51 std::string extension_id; | |
52 void* background_host_ptr; // used in IncrementLazyKeepaliveCount | |
53 | |
54 MessagePort() | |
55 : process(NULL), | |
56 routing_id(MSG_ROUTING_CONTROL), | |
57 background_host_ptr(NULL) {} | |
58 MessagePort(content::RenderProcessHost* process, | |
59 int routing_id, | |
60 const std::string& extension_id) | |
61 : process(process), | |
62 routing_id(routing_id), | |
63 extension_id(extension_id), | |
64 background_host_ptr(NULL) {} | |
65 }; | |
66 | |
67 struct ExtensionMessageService::MessageChannel { | |
68 ExtensionMessageService::MessagePort opener; | |
69 ExtensionMessageService::MessagePort receiver; | |
70 }; | |
71 | |
72 struct ExtensionMessageService::OpenChannelParams { | |
73 content::RenderProcessHost* source; | |
74 std::string tab_json; | |
75 MessagePort receiver; | |
76 int receiver_port_id; | |
77 std::string source_extension_id; | |
78 std::string target_extension_id; | |
79 std::string channel_name; | |
80 | |
81 OpenChannelParams(content::RenderProcessHost* source, | |
82 const std::string& tab_json, | |
83 const MessagePort& receiver, | |
84 int receiver_port_id, | |
85 const std::string& source_extension_id, | |
86 const std::string& target_extension_id, | |
87 const std::string& channel_name) | |
88 : source(source), | |
89 tab_json(tab_json), | |
90 receiver(receiver), | |
91 receiver_port_id(receiver_port_id), | |
92 source_extension_id(source_extension_id), | |
93 target_extension_id(target_extension_id), | |
94 channel_name(channel_name) {} | |
95 }; | |
96 | |
97 namespace { | |
98 | |
99 static base::StaticAtomicSequenceNumber g_next_channel_id; | |
100 | |
101 static void DispatchOnConnect(const ExtensionMessageService::MessagePort& port, | |
102 int dest_port_id, | |
103 const std::string& channel_name, | |
104 const std::string& tab_json, | |
105 const std::string& source_extension_id, | |
106 const std::string& target_extension_id) { | |
107 port.process->Send(new ExtensionMsg_DispatchOnConnect( | |
108 port.routing_id, dest_port_id, channel_name, | |
109 tab_json, source_extension_id, target_extension_id)); | |
110 } | |
111 | |
112 static void DispatchOnDisconnect( | |
113 const ExtensionMessageService::MessagePort& port, int source_port_id, | |
114 bool connection_error) { | |
115 port.process->Send(new ExtensionMsg_DispatchOnDisconnect( | |
116 port.routing_id, source_port_id, connection_error)); | |
117 } | |
118 | |
119 static void DispatchOnMessage(const ExtensionMessageService::MessagePort& port, | |
120 const std::string& message, int target_port_id) { | |
121 port.process->Send(new ExtensionMsg_DeliverMessage( | |
122 port.routing_id, target_port_id, message)); | |
123 } | |
124 | |
125 static content::RenderProcessHost* GetExtensionProcess( | |
126 Profile* profile, const std::string& extension_id) { | |
127 SiteInstance* site_instance = | |
128 profile->GetExtensionProcessManager()->GetSiteInstanceForURL( | |
129 extensions::Extension::GetBaseURLFromExtensionId(extension_id)); | |
130 | |
131 if (!site_instance->HasProcess()) | |
132 return NULL; | |
133 | |
134 return site_instance->GetProcess(); | |
135 } | |
136 | |
137 static void IncrementLazyKeepaliveCount( | |
138 ExtensionMessageService::MessagePort* port) { | |
139 Profile* profile = | |
140 Profile::FromBrowserContext(port->process->GetBrowserContext()); | |
141 ExtensionProcessManager* pm = | |
142 extensions::ExtensionSystem::Get(profile)->process_manager(); | |
143 ExtensionHost* host = pm->GetBackgroundHostForExtension(port->extension_id); | |
144 if (host && host->extension()->has_lazy_background_page()) | |
145 pm->IncrementLazyKeepaliveCount(host->extension()); | |
146 | |
147 // Keep track of the background host, so when we decrement, we only do so if | |
148 // the host hasn't reloaded. | |
149 port->background_host_ptr = host; | |
150 } | |
151 | |
152 static void DecrementLazyKeepaliveCount( | |
153 ExtensionMessageService::MessagePort* port) { | |
154 Profile* profile = | |
155 Profile::FromBrowserContext(port->process->GetBrowserContext()); | |
156 ExtensionProcessManager* pm = | |
157 extensions::ExtensionSystem::Get(profile)->process_manager(); | |
158 ExtensionHost* host = pm->GetBackgroundHostForExtension(port->extension_id); | |
159 if (host && host == port->background_host_ptr) | |
160 pm->DecrementLazyKeepaliveCount(host->extension()); | |
161 } | |
162 | |
163 } // namespace | |
164 | |
165 // static | |
166 void ExtensionMessageService::AllocatePortIdPair(int* port1, int* port2) { | |
167 int channel_id = g_next_channel_id.GetNext(); | |
168 int port1_id = channel_id * 2; | |
169 int port2_id = channel_id * 2 + 1; | |
170 | |
171 // Sanity checks to make sure our channel<->port converters are correct. | |
172 DCHECK(IS_OPENER_PORT_ID(port1_id)); | |
173 DCHECK(GET_OPPOSITE_PORT_ID(port1_id) == port2_id); | |
174 DCHECK(GET_OPPOSITE_PORT_ID(port2_id) == port1_id); | |
175 DCHECK(GET_CHANNEL_ID(port1_id) == GET_CHANNEL_ID(port2_id)); | |
176 DCHECK(GET_CHANNEL_ID(port1_id) == channel_id); | |
177 DCHECK(GET_CHANNEL_OPENER_ID(channel_id) == port1_id); | |
178 DCHECK(GET_CHANNEL_RECEIVERS_ID(channel_id) == port2_id); | |
179 | |
180 *port1 = port1_id; | |
181 *port2 = port2_id; | |
182 } | |
183 | |
184 ExtensionMessageService::ExtensionMessageService( | |
185 extensions::LazyBackgroundTaskQueue* queue) | |
186 : lazy_background_task_queue_(queue) { | |
187 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, | |
188 content::NotificationService::AllBrowserContextsAndSources()); | |
189 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, | |
190 content::NotificationService::AllBrowserContextsAndSources()); | |
191 } | |
192 | |
193 ExtensionMessageService::~ExtensionMessageService() { | |
194 STLDeleteContainerPairSecondPointers(channels_.begin(), channels_.end()); | |
195 channels_.clear(); | |
196 } | |
197 | |
198 void ExtensionMessageService::OpenChannelToExtension( | |
199 int source_process_id, int source_routing_id, int receiver_port_id, | |
200 const std::string& source_extension_id, | |
201 const std::string& target_extension_id, | |
202 const std::string& channel_name) { | |
203 content::RenderProcessHost* source = | |
204 content::RenderProcessHost::FromID(source_process_id); | |
205 if (!source) | |
206 return; | |
207 Profile* profile = Profile::FromBrowserContext(source->GetBrowserContext()); | |
208 | |
209 // Note: we use the source's profile here. If the source is an incognito | |
210 // process, we will use the incognito EPM to find the right extension process, | |
211 // which depends on whether the extension uses spanning or split mode. | |
212 MessagePort receiver( | |
213 GetExtensionProcess(profile, target_extension_id), | |
214 MSG_ROUTING_CONTROL, | |
215 target_extension_id); | |
216 WebContents* source_contents = tab_util::GetWebContentsByID( | |
217 source_process_id, source_routing_id); | |
218 | |
219 // Include info about the opener's tab (if it was a tab). | |
220 std::string tab_json = "null"; | |
221 if (source_contents) { | |
222 scoped_ptr<DictionaryValue> tab_value( | |
223 ExtensionTabUtil::CreateTabValue(source_contents)); | |
224 base::JSONWriter::Write(tab_value.get(), &tab_json); | |
225 } | |
226 | |
227 OpenChannelParams params(source, tab_json, receiver, receiver_port_id, | |
228 source_extension_id, target_extension_id, | |
229 channel_name); | |
230 | |
231 // The target might be a lazy background page. In that case, we have to check | |
232 // if it is loaded and ready, and if not, queue up the task and load the | |
233 // page. | |
234 if (MaybeAddPendingOpenChannelTask(profile, params)) | |
235 return; | |
236 | |
237 OpenChannelImpl(params); | |
238 } | |
239 | |
240 void ExtensionMessageService::OpenChannelToTab( | |
241 int source_process_id, int source_routing_id, int receiver_port_id, | |
242 int tab_id, const std::string& extension_id, | |
243 const std::string& channel_name) { | |
244 content::RenderProcessHost* source = | |
245 content::RenderProcessHost::FromID(source_process_id); | |
246 if (!source) | |
247 return; | |
248 Profile* profile = Profile::FromBrowserContext(source->GetBrowserContext()); | |
249 | |
250 TabContents* contents = NULL; | |
251 MessagePort receiver; | |
252 if (ExtensionTabUtil::GetTabById(tab_id, profile, true, | |
253 NULL, NULL, &contents, NULL)) { | |
254 receiver.process = contents->web_contents()->GetRenderProcessHost(); | |
255 receiver.routing_id = | |
256 contents->web_contents()->GetRenderViewHost()->GetRoutingID(); | |
257 receiver.extension_id = extension_id; | |
258 } | |
259 | |
260 if (contents && contents->web_contents()->GetController().NeedsReload()) { | |
261 // The tab isn't loaded yet. Don't attempt to connect. Treat this as a | |
262 // disconnect. | |
263 DispatchOnDisconnect(MessagePort(source, MSG_ROUTING_CONTROL, extension_id), | |
264 GET_OPPOSITE_PORT_ID(receiver_port_id), true); | |
265 return; | |
266 } | |
267 | |
268 WebContents* source_contents = tab_util::GetWebContentsByID( | |
269 source_process_id, source_routing_id); | |
270 | |
271 // Include info about the opener's tab (if it was a tab). | |
272 std::string tab_json = "null"; | |
273 if (source_contents) { | |
274 scoped_ptr<DictionaryValue> tab_value( | |
275 ExtensionTabUtil::CreateTabValue(source_contents)); | |
276 base::JSONWriter::Write(tab_value.get(), &tab_json); | |
277 } | |
278 | |
279 OpenChannelParams params(source, tab_json, receiver, receiver_port_id, | |
280 extension_id, extension_id, channel_name); | |
281 OpenChannelImpl(params); | |
282 } | |
283 | |
284 bool ExtensionMessageService::OpenChannelImpl(const OpenChannelParams& params) { | |
285 if (!params.source) | |
286 return false; // Closed while in flight. | |
287 | |
288 if (!params.receiver.process) { | |
289 // Treat it as a disconnect. | |
290 DispatchOnDisconnect(MessagePort(params.source, MSG_ROUTING_CONTROL, ""), | |
291 GET_OPPOSITE_PORT_ID(params.receiver_port_id), true); | |
292 return false; | |
293 } | |
294 | |
295 // Add extra paranoid CHECKs, since we have crash reports of this being NULL. | |
296 // http://code.google.com/p/chromium/issues/detail?id=19067 | |
297 CHECK(params.receiver.process); | |
298 | |
299 MessageChannel* channel(new MessageChannel); | |
300 channel->opener = MessagePort(params.source, MSG_ROUTING_CONTROL, | |
301 params.source_extension_id); | |
302 channel->receiver = params.receiver; | |
303 | |
304 CHECK(params.receiver.process); | |
305 | |
306 int channel_id = GET_CHANNEL_ID(params.receiver_port_id); | |
307 CHECK(channels_.find(channel_id) == channels_.end()); | |
308 channels_[channel_id] = channel; | |
309 pending_channels_.erase(channel_id); | |
310 | |
311 CHECK(params.receiver.process); | |
312 | |
313 // Send the connect event to the receiver. Give it the opener's port ID (the | |
314 // opener has the opposite port ID). | |
315 DispatchOnConnect(params.receiver, params.receiver_port_id, | |
316 params.channel_name, params.tab_json, | |
317 params.source_extension_id, params.target_extension_id); | |
318 | |
319 // Keep both ends of the channel alive until the channel is closed. | |
320 IncrementLazyKeepaliveCount(&channel->opener); | |
321 IncrementLazyKeepaliveCount(&channel->receiver); | |
322 return true; | |
323 } | |
324 | |
325 void ExtensionMessageService::CloseChannel(int port_id, bool connection_error) { | |
326 // Note: The channel might be gone already, if the other side closed first. | |
327 int channel_id = GET_CHANNEL_ID(port_id); | |
328 MessageChannelMap::iterator it = channels_.find(channel_id); | |
329 if (it == channels_.end()) { | |
330 PendingChannelMap::iterator pending = pending_channels_.find(channel_id); | |
331 if (pending != pending_channels_.end()) { | |
332 lazy_background_task_queue_->AddPendingTask( | |
333 pending->second.first, pending->second.second, | |
334 base::Bind(&ExtensionMessageService::PendingCloseChannel, | |
335 base::Unretained(this), port_id, connection_error)); | |
336 } | |
337 return; | |
338 } | |
339 CloseChannelImpl(it, port_id, connection_error, true); | |
340 } | |
341 | |
342 void ExtensionMessageService::CloseChannelImpl( | |
343 MessageChannelMap::iterator channel_iter, int closing_port_id, | |
344 bool connection_error, bool notify_other_port) { | |
345 MessageChannel* channel = channel_iter->second; | |
346 | |
347 // Notify the other side. | |
348 if (notify_other_port) { | |
349 const MessagePort& port = IS_OPENER_PORT_ID(closing_port_id) ? | |
350 channel->receiver : channel->opener; | |
351 DispatchOnDisconnect(port, GET_OPPOSITE_PORT_ID(closing_port_id), | |
352 connection_error); | |
353 } | |
354 | |
355 // Balance the addrefs in OpenChannelImpl. | |
356 DecrementLazyKeepaliveCount(&channel->opener); | |
357 DecrementLazyKeepaliveCount(&channel->receiver); | |
358 | |
359 delete channel_iter->second; | |
360 channels_.erase(channel_iter); | |
361 } | |
362 | |
363 void ExtensionMessageService::PostMessageFromRenderer( | |
364 int source_port_id, const std::string& message) { | |
365 int channel_id = GET_CHANNEL_ID(source_port_id); | |
366 MessageChannelMap::iterator iter = channels_.find(channel_id); | |
367 if (iter == channels_.end()) { | |
368 // If this channel is pending, queue up the PostMessage to run once | |
369 // the channel opens. | |
370 PendingChannelMap::iterator pending = pending_channels_.find(channel_id); | |
371 if (pending != pending_channels_.end()) { | |
372 lazy_background_task_queue_->AddPendingTask( | |
373 pending->second.first, pending->second.second, | |
374 base::Bind(&ExtensionMessageService::PendingPostMessage, | |
375 base::Unretained(this), source_port_id, message)); | |
376 } | |
377 return; | |
378 } | |
379 | |
380 // Figure out which port the ID corresponds to. | |
381 int dest_port_id = GET_OPPOSITE_PORT_ID(source_port_id); | |
382 const MessagePort& port = IS_OPENER_PORT_ID(dest_port_id) ? | |
383 iter->second->opener : iter->second->receiver; | |
384 | |
385 DispatchOnMessage(port, message, dest_port_id); | |
386 } | |
387 | |
388 void ExtensionMessageService::Observe( | |
389 int type, | |
390 const content::NotificationSource& source, | |
391 const content::NotificationDetails& details) { | |
392 switch (type) { | |
393 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: | |
394 case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: { | |
395 content::RenderProcessHost* renderer = | |
396 content::Source<content::RenderProcessHost>(source).ptr(); | |
397 OnProcessClosed(renderer); | |
398 break; | |
399 } | |
400 default: | |
401 NOTREACHED(); | |
402 return; | |
403 } | |
404 } | |
405 | |
406 void ExtensionMessageService::OnProcessClosed( | |
407 content::RenderProcessHost* process) { | |
408 // Close any channels that share this renderer. We notify the opposite | |
409 // port that his pair has closed. | |
410 for (MessageChannelMap::iterator it = channels_.begin(); | |
411 it != channels_.end(); ) { | |
412 MessageChannelMap::iterator current = it++; | |
413 // If both sides are the same renderer, and it is closing, there is no | |
414 // "other" port, so there's no need to notify it. | |
415 bool notify_other_port = | |
416 current->second->opener.process != current->second->receiver.process; | |
417 | |
418 if (current->second->opener.process == process) { | |
419 CloseChannelImpl(current, GET_CHANNEL_OPENER_ID(current->first), | |
420 false, notify_other_port); | |
421 } else if (current->second->receiver.process == process) { | |
422 CloseChannelImpl(current, GET_CHANNEL_RECEIVERS_ID(current->first), | |
423 false, notify_other_port); | |
424 } | |
425 } | |
426 } | |
427 | |
428 bool ExtensionMessageService::MaybeAddPendingOpenChannelTask( | |
429 Profile* profile, | |
430 const OpenChannelParams& params) { | |
431 ExtensionService* service = profile->GetExtensionService(); | |
432 const std::string& extension_id = params.target_extension_id; | |
433 const extensions::Extension* extension = service->extensions()->GetByID( | |
434 extension_id); | |
435 if (extension && extension->has_lazy_background_page()) { | |
436 // If the extension uses spanning incognito mode, make sure we're always | |
437 // using the original profile since that is what the extension process | |
438 // will use. | |
439 if (!extension->incognito_split_mode()) | |
440 profile = profile->GetOriginalProfile(); | |
441 | |
442 if (lazy_background_task_queue_->ShouldEnqueueTask(profile, extension)) { | |
443 lazy_background_task_queue_->AddPendingTask(profile, extension_id, | |
444 base::Bind(&ExtensionMessageService::PendingOpenChannel, | |
445 base::Unretained(this), params, params.source->GetID())); | |
446 pending_channels_[GET_CHANNEL_ID(params.receiver_port_id)] = | |
447 PendingChannel(profile, extension_id); | |
448 return true; | |
449 } | |
450 } | |
451 | |
452 return false; | |
453 } | |
454 | |
455 void ExtensionMessageService::PendingOpenChannel( | |
456 const OpenChannelParams& params_in, | |
457 int source_process_id, | |
458 ExtensionHost* host) { | |
459 if (!host) | |
460 return; // TODO(mpcomplete): notify source of disconnect? | |
461 | |
462 // Re-lookup the source process since it may no longer be valid. | |
463 OpenChannelParams params = params_in; | |
464 params.source = content::RenderProcessHost::FromID(source_process_id); | |
465 if (!params.source) | |
466 return; | |
467 | |
468 params.receiver = MessagePort(host->render_process_host(), | |
469 MSG_ROUTING_CONTROL, | |
470 params.target_extension_id); | |
471 OpenChannelImpl(params); | |
472 } | |
OLD | NEW |