]> git.saurik.com Git - apple/javascriptcore.git/blame - inspector/remote/RemoteInspector.mm
JavaScriptCore-7600.1.4.17.5.tar.gz
[apple/javascriptcore.git] / inspector / remote / RemoteInspector.mm
CommitLineData
81345200
A
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
46namespace Inspector {
47
48static 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
60bool RemoteInspector::startEnabled = true;
61
62void RemoteInspector::startDisabled()
63{
64 RemoteInspector::startEnabled = false;
65}
66
67RemoteInspector& 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
81RemoteInspector::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
93unsigned 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
102void 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
116void 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
134void 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
148void 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
168void 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
179void 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
195void RemoteInspector::stop()
196{
197 std::lock_guard<std::mutex> lock(m_mutex);
198
199 stopInternal(StopSource::API);
200}
201
202void 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
233void 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
253void 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
269void 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
296void 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
316void RemoteInspector::xpcConnectionUnhandledMessage(RemoteInspectorXPCConnection*, xpc_object_t)
317{
318 // Intentionally ignored.
319}
320
321#pragma mark - Listings
322
323NSDictionary *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
358void 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
381void 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
399void 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
413void 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
451void 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
467void 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
493void RemoteInspector::receivedGetListingMessage(NSDictionary *)
494{
495 pushListingNow();
496}
497
498void 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
522void 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
548void 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)