]> git.saurik.com Git - apple/javascriptcore.git/blob - inspector/remote/RemoteInspector.mm
9e058012dd391acd4b32dd82bfbbc6217432b76b
[apple/javascriptcore.git] / inspector / remote / RemoteInspector.mm
1 /*
2 * Copyright (C) 2013 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #import "config.h"
27 #import "RemoteInspector.h"
28
29 #if ENABLE(REMOTE_INSPECTOR)
30
31 #import "InitializeThreading.h"
32 #import "RemoteInspectorConstants.h"
33 #import "RemoteInspectorDebuggable.h"
34 #import "RemoteInspectorDebuggableConnection.h"
35 #import <Foundation/Foundation.h>
36 #import <notify.h>
37 #import <wtf/Assertions.h>
38 #import <wtf/NeverDestroyed.h>
39 #import <wtf/text/WTFString.h>
40 #import <xpc/xpc.h>
41
42 #if PLATFORM(IOS)
43 #import <wtf/ios/WebCoreThread.h>
44 #endif
45
46 namespace Inspector {
47
48 static void dispatchAsyncOnQueueSafeForAnyDebuggable(void (^block)())
49 {
50 #if PLATFORM(IOS)
51 if (WebCoreWebThreadIsEnabled && WebCoreWebThreadIsEnabled()) {
52 WebCoreWebThreadRun(block);
53 return;
54 }
55 #endif
56
57 dispatch_async(dispatch_get_main_queue(), block);
58 }
59
60 bool RemoteInspector::startEnabled = true;
61
62 void RemoteInspector::startDisabled()
63 {
64 RemoteInspector::startEnabled = false;
65 }
66
67 RemoteInspector& RemoteInspector::shared()
68 {
69 static NeverDestroyed<RemoteInspector> shared;
70
71 static dispatch_once_t once;
72 dispatch_once(&once, ^{
73 JSC::initializeThreading();
74 if (RemoteInspector::startEnabled)
75 shared.get().start();
76 });
77
78 return shared;
79 }
80
81 RemoteInspector::RemoteInspector()
82 : m_xpcQueue(dispatch_queue_create("com.apple.JavaScriptCore.remote-inspector-xpc", DISPATCH_QUEUE_SERIAL))
83 , m_nextAvailableIdentifier(1)
84 , m_notifyToken(0)
85 , m_enabled(false)
86 , m_hasActiveDebugSession(false)
87 , m_pushScheduled(false)
88 , m_parentProcessIdentifier(0)
89 , m_shouldSendParentProcessInformation(false)
90 {
91 }
92
93 unsigned RemoteInspector::nextAvailableIdentifier()
94 {
95 unsigned nextValidIdentifier;
96 do {
97 nextValidIdentifier = m_nextAvailableIdentifier++;
98 } while (!nextValidIdentifier || nextValidIdentifier == std::numeric_limits<unsigned>::max() || m_debuggableMap.contains(nextValidIdentifier));
99 return nextValidIdentifier;
100 }
101
102 void RemoteInspector::registerDebuggable(RemoteInspectorDebuggable* debuggable)
103 {
104 std::lock_guard<std::mutex> lock(m_mutex);
105
106 unsigned identifier = nextAvailableIdentifier();
107 debuggable->setIdentifier(identifier);
108
109 auto result = m_debuggableMap.set(identifier, std::make_pair(debuggable, debuggable->info()));
110 ASSERT_UNUSED(result, result.isNewEntry);
111
112 if (debuggable->remoteDebuggingAllowed())
113 pushListingSoon();
114 }
115
116 void RemoteInspector::unregisterDebuggable(RemoteInspectorDebuggable* debuggable)
117 {
118 std::lock_guard<std::mutex> lock(m_mutex);
119
120 unsigned identifier = debuggable->identifier();
121 if (!identifier)
122 return;
123
124 bool wasRemoved = m_debuggableMap.remove(identifier);
125 ASSERT_UNUSED(wasRemoved, wasRemoved);
126
127 if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.take(identifier))
128 connection->closeFromDebuggable();
129
130 if (debuggable->remoteDebuggingAllowed())
131 pushListingSoon();
132 }
133
134 void RemoteInspector::updateDebuggable(RemoteInspectorDebuggable* debuggable)
135 {
136 std::lock_guard<std::mutex> lock(m_mutex);
137
138 unsigned identifier = debuggable->identifier();
139 if (!identifier)
140 return;
141
142 auto result = m_debuggableMap.set(identifier, std::make_pair(debuggable, debuggable->info()));
143 ASSERT_UNUSED(result, !result.isNewEntry);
144
145 pushListingSoon();
146 }
147
148 void RemoteInspector::sendMessageToRemoteFrontend(unsigned identifier, const String& message)
149 {
150 std::lock_guard<std::mutex> lock(m_mutex);
151
152 if (!m_xpcConnection)
153 return;
154
155 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier);
156 if (!connection)
157 return;
158
159 NSDictionary *userInfo = @{
160 WIRRawDataKey: [static_cast<NSString *>(message) dataUsingEncoding:NSUTF8StringEncoding],
161 WIRConnectionIdentifierKey: connection->connectionIdentifier(),
162 WIRDestinationKey: connection->destination()
163 };
164
165 m_xpcConnection->sendMessage(WIRRawDataMessage, userInfo);
166 }
167
168 void RemoteInspector::setupFailed(unsigned identifier)
169 {
170 std::lock_guard<std::mutex> lock(m_mutex);
171
172 m_connectionMap.remove(identifier);
173
174 updateHasActiveDebugSession();
175
176 pushListingSoon();
177 }
178
179 void RemoteInspector::start()
180 {
181 std::lock_guard<std::mutex> lock(m_mutex);
182
183 if (m_enabled)
184 return;
185
186 m_enabled = true;
187
188 notify_register_dispatch(WIRServiceAvailableNotification, &m_notifyToken, m_xpcQueue, ^(int) {
189 RemoteInspector::shared().setupXPCConnectionIfNeeded();
190 });
191
192 notify_post(WIRServiceAvailabilityCheckNotification);
193 }
194
195 void RemoteInspector::stop()
196 {
197 std::lock_guard<std::mutex> lock(m_mutex);
198
199 stopInternal(StopSource::API);
200 }
201
202 void RemoteInspector::stopInternal(StopSource source)
203 {
204 if (!m_enabled)
205 return;
206
207 m_enabled = false;
208
209 m_pushScheduled = false;
210
211 for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it)
212 it->value->close();
213 m_connectionMap.clear();
214
215 updateHasActiveDebugSession();
216
217 if (m_xpcConnection) {
218 switch (source) {
219 case StopSource::API:
220 m_xpcConnection->close();
221 break;
222 case StopSource::XPCMessage:
223 m_xpcConnection->closeFromMessage();
224 break;
225 }
226
227 m_xpcConnection = nullptr;
228 }
229
230 notify_cancel(m_notifyToken);
231 }
232
233 void RemoteInspector::setupXPCConnectionIfNeeded()
234 {
235 std::lock_guard<std::mutex> lock(m_mutex);
236
237 if (m_xpcConnection)
238 return;
239
240 xpc_connection_t connection = xpc_connection_create_mach_service(WIRXPCMachPortName, m_xpcQueue, 0);
241 if (!connection)
242 return;
243
244 m_xpcConnection = adoptRef(new RemoteInspectorXPCConnection(connection, m_xpcQueue, this));
245 m_xpcConnection->sendMessage(@"syn", nil); // Send a simple message to initialize the XPC connection.
246 xpc_release(connection);
247
248 pushListingSoon();
249 }
250
251 #pragma mark - Proxy Application Information
252
253 void RemoteInspector::setParentProcessInformation(pid_t pid, RetainPtr<CFDataRef> auditData)
254 {
255 std::lock_guard<std::mutex> lock(m_mutex);
256
257 if (m_parentProcessIdentifier || m_parentProcessAuditData)
258 return;
259
260 m_parentProcessIdentifier = pid;
261 m_parentProcessAuditData = auditData;
262
263 if (m_shouldSendParentProcessInformation)
264 receivedProxyApplicationSetupMessage(nil);
265 }
266
267 #pragma mark - RemoteInspectorXPCConnection::Client
268
269 void RemoteInspector::xpcConnectionReceivedMessage(RemoteInspectorXPCConnection*, NSString *messageName, NSDictionary *userInfo)
270 {
271 std::lock_guard<std::mutex> lock(m_mutex);
272
273 if ([messageName isEqualToString:WIRPermissionDenied]) {
274 stopInternal(StopSource::XPCMessage);
275 return;
276 }
277
278 if ([messageName isEqualToString:WIRSocketDataMessage])
279 receivedDataMessage(userInfo);
280 else if ([messageName isEqualToString:WIRSocketSetupMessage])
281 receivedSetupMessage(userInfo);
282 else if ([messageName isEqualToString:WIRWebPageCloseMessage])
283 receivedDidCloseMessage(userInfo);
284 else if ([messageName isEqualToString:WIRApplicationGetListingMessage])
285 receivedGetListingMessage(userInfo);
286 else if ([messageName isEqualToString:WIRIndicateMessage])
287 receivedIndicateMessage(userInfo);
288 else if ([messageName isEqualToString:WIRProxyApplicationSetupMessage])
289 receivedProxyApplicationSetupMessage(userInfo);
290 else if ([messageName isEqualToString:WIRConnectionDiedMessage])
291 receivedConnectionDiedMessage(userInfo);
292 else
293 NSLog(@"Unrecognized RemoteInspector XPC Message: %@", messageName);
294 }
295
296 void RemoteInspector::xpcConnectionFailed(RemoteInspectorXPCConnection* connection)
297 {
298 std::lock_guard<std::mutex> lock(m_mutex);
299
300 ASSERT(connection == m_xpcConnection);
301 if (connection != m_xpcConnection)
302 return;
303
304 m_pushScheduled = false;
305
306 for (auto it = m_connectionMap.begin(), end = m_connectionMap.end(); it != end; ++it)
307 it->value->close();
308 m_connectionMap.clear();
309
310 updateHasActiveDebugSession();
311
312 // The connection will close itself.
313 m_xpcConnection = nullptr;
314 }
315
316 void RemoteInspector::xpcConnectionUnhandledMessage(RemoteInspectorXPCConnection*, xpc_object_t)
317 {
318 // Intentionally ignored.
319 }
320
321 #pragma mark - Listings
322
323 NSDictionary *RemoteInspector::listingForDebuggable(const RemoteInspectorDebuggableInfo& debuggableInfo) const
324 {
325 NSMutableDictionary *debuggableDetails = [NSMutableDictionary dictionary];
326
327 [debuggableDetails setObject:@(debuggableInfo.identifier) forKey:WIRPageIdentifierKey];
328
329 switch (debuggableInfo.type) {
330 case RemoteInspectorDebuggable::JavaScript: {
331 NSString *name = debuggableInfo.name;
332 [debuggableDetails setObject:name forKey:WIRTitleKey];
333 [debuggableDetails setObject:WIRTypeJavaScript forKey:WIRTypeKey];
334 break;
335 }
336 case RemoteInspectorDebuggable::Web: {
337 NSString *url = debuggableInfo.url;
338 NSString *title = debuggableInfo.name;
339 [debuggableDetails setObject:url forKey:WIRURLKey];
340 [debuggableDetails setObject:title forKey:WIRTitleKey];
341 [debuggableDetails setObject:WIRTypeWeb forKey:WIRTypeKey];
342 break;
343 }
344 default:
345 ASSERT_NOT_REACHED();
346 break;
347 }
348
349 if (RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(debuggableInfo.identifier))
350 [debuggableDetails setObject:connection->connectionIdentifier() forKey:WIRConnectionIdentifierKey];
351
352 if (debuggableInfo.hasLocalDebugger)
353 [debuggableDetails setObject:@YES forKey:WIRHasLocalDebuggerKey];
354
355 return debuggableDetails;
356 }
357
358 void RemoteInspector::pushListingNow()
359 {
360 ASSERT(m_xpcConnection);
361 if (!m_xpcConnection)
362 return;
363
364 m_pushScheduled = false;
365
366 RetainPtr<NSMutableDictionary> response = adoptNS([[NSMutableDictionary alloc] init]);
367 for (auto it = m_debuggableMap.begin(), end = m_debuggableMap.end(); it != end; ++it) {
368 const RemoteInspectorDebuggableInfo& debuggableInfo = it->value.second;
369 if (debuggableInfo.remoteDebuggingAllowed) {
370 NSDictionary *details = listingForDebuggable(debuggableInfo);
371 [response setObject:details forKey:[NSString stringWithFormat:@"%u", debuggableInfo.identifier]];
372 }
373 }
374
375 RetainPtr<NSMutableDictionary> outgoing = adoptNS([[NSMutableDictionary alloc] init]);
376 [outgoing setObject:response.get() forKey:WIRListingKey];
377
378 m_xpcConnection->sendMessage(WIRListingMessage, outgoing.get());
379 }
380
381 void RemoteInspector::pushListingSoon()
382 {
383 if (!m_xpcConnection)
384 return;
385
386 if (m_pushScheduled)
387 return;
388
389 m_pushScheduled = true;
390 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.2 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
391 std::lock_guard<std::mutex> lock(m_mutex);
392 if (m_pushScheduled)
393 pushListingNow();
394 });
395 }
396
397 #pragma mark - Active Debugger Sessions
398
399 void RemoteInspector::updateHasActiveDebugSession()
400 {
401 bool hasActiveDebuggerSession = !m_connectionMap.isEmpty();
402 if (hasActiveDebuggerSession == m_hasActiveDebugSession)
403 return;
404
405 m_hasActiveDebugSession = hasActiveDebuggerSession;
406
407 // FIXME: Expose some way to access this state in an embedder.
408 // Legacy iOS WebKit 1 had a notification. This will need to be smarter with WebKit2.
409 }
410
411 #pragma mark - Received XPC Messages
412
413 void RemoteInspector::receivedSetupMessage(NSDictionary *userInfo)
414 {
415 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
416 if (!pageId)
417 return;
418
419 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
420 if (!connectionIdentifier)
421 return;
422
423 NSString *sender = [userInfo objectForKey:WIRSenderKey];
424 if (!sender)
425 return;
426
427 unsigned identifier = [pageId unsignedIntValue];
428 if (m_connectionMap.contains(identifier))
429 return;
430
431 auto it = m_debuggableMap.find(identifier);
432 if (it == m_debuggableMap.end())
433 return;
434
435 // Attempt to create a connection. This may fail if the page already has an inspector or if it disallows inspection.
436 RemoteInspectorDebuggable* debuggable = it->value.first;
437 RemoteInspectorDebuggableInfo debuggableInfo = it->value.second;
438 RefPtr<RemoteInspectorDebuggableConnection> connection = adoptRef(new RemoteInspectorDebuggableConnection(debuggable, connectionIdentifier, sender, debuggableInfo.type));
439 if (!connection->setup()) {
440 connection->close();
441 return;
442 }
443
444 m_connectionMap.set(identifier, connection.release());
445
446 updateHasActiveDebugSession();
447
448 pushListingSoon();
449 }
450
451 void RemoteInspector::receivedDataMessage(NSDictionary *userInfo)
452 {
453 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
454 if (!pageId)
455 return;
456
457 unsigned pageIdentifier = [pageId unsignedIntValue];
458 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(pageIdentifier);
459 if (!connection)
460 return;
461
462 NSData *data = [userInfo objectForKey:WIRSocketDataKey];
463 RetainPtr<NSString> message = adoptNS([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
464 connection->sendMessageToBackend(message.get());
465 }
466
467 void RemoteInspector::receivedDidCloseMessage(NSDictionary *userInfo)
468 {
469 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
470 if (!pageId)
471 return;
472
473 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
474 if (!connectionIdentifier)
475 return;
476
477 unsigned identifier = [pageId unsignedIntValue];
478 RefPtr<RemoteInspectorDebuggableConnection> connection = m_connectionMap.get(identifier);
479 if (!connection)
480 return;
481
482 if (![connectionIdentifier isEqualToString:connection->connectionIdentifier()])
483 return;
484
485 connection->close();
486 m_connectionMap.remove(identifier);
487
488 updateHasActiveDebugSession();
489
490 pushListingSoon();
491 }
492
493 void RemoteInspector::receivedGetListingMessage(NSDictionary *)
494 {
495 pushListingNow();
496 }
497
498 void RemoteInspector::receivedIndicateMessage(NSDictionary *userInfo)
499 {
500 NSNumber *pageId = [userInfo objectForKey:WIRPageIdentifierKey];
501 if (!pageId)
502 return;
503
504 unsigned identifier = [pageId unsignedIntValue];
505 BOOL indicateEnabled = [[userInfo objectForKey:WIRIndicateEnabledKey] boolValue];
506
507 dispatchAsyncOnQueueSafeForAnyDebuggable(^{
508 RemoteInspectorDebuggable* debuggable = nullptr;
509 {
510 std::lock_guard<std::mutex> lock(m_mutex);
511
512 auto it = m_debuggableMap.find(identifier);
513 if (it == m_debuggableMap.end())
514 return;
515
516 debuggable = it->value.first;
517 }
518 debuggable->setIndicating(indicateEnabled);
519 });
520 }
521
522 void RemoteInspector::receivedProxyApplicationSetupMessage(NSDictionary *)
523 {
524 ASSERT(m_xpcConnection);
525 if (!m_xpcConnection)
526 return;
527
528 if (!m_parentProcessIdentifier || !m_parentProcessAuditData) {
529 // We are a proxy application without parent process information.
530 // Wait a bit for the information, but give up after a second.
531 m_shouldSendParentProcessInformation = true;
532 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
533 std::lock_guard<std::mutex> lock(m_mutex);
534 if (m_shouldSendParentProcessInformation)
535 stopInternal(StopSource::XPCMessage);
536 });
537 return;
538 }
539
540 m_shouldSendParentProcessInformation = false;
541
542 m_xpcConnection->sendMessage(WIRProxyApplicationSetupResponseMessage, @{
543 WIRProxyApplicationParentPIDKey: @(m_parentProcessIdentifier),
544 WIRProxyApplicationParentAuditDataKey: (NSData *)m_parentProcessAuditData.get(),
545 });
546 }
547
548 void RemoteInspector::receivedConnectionDiedMessage(NSDictionary *userInfo)
549 {
550 NSString *connectionIdentifier = [userInfo objectForKey:WIRConnectionIdentifierKey];
551 if (!connectionIdentifier)
552 return;
553
554 auto it = m_connectionMap.begin();
555 auto end = m_connectionMap.end();
556 for (; it != end; ++it) {
557 if ([connectionIdentifier isEqualToString:it->value->connectionIdentifier()])
558 break;
559 }
560
561 if (it == end)
562 return;
563
564 RefPtr<RemoteInspectorDebuggableConnection> connection = it->value;
565 connection->close();
566 m_connectionMap.remove(it);
567
568 updateHasActiveDebugSession();
569 }
570
571 } // namespace Inspector
572
573 #endif // ENABLE(REMOTE_INSPECTOR)